Issue with touch - critical bug

@nite @slackmoehrle

Hi, it seems there is some issue in cocos2d-x core for handling touches.

We using cocos2d-x 3.15.1 C++ version, in our case we having issue on game over -> when user touching game screen and Interstitial ads from heyzapp appear - touches get disabled in game over screen.

After research we found that when Heyzapp ads appear cocos2d-x not sending touch end signal and it thinking that we still holding finger on screen . In cocos core exist such function:

static int getUnUsedIndex()
    {
        int i;
        int temp = g_indexBitsUsed;
        
        for (i = 0; i < EventTouch::MAX_TOUCHES; i++) {
            if (! (temp & 0x00000001)) {
                g_indexBitsUsed |= (1 <<  i);
                return i;
            }
            
            temp >>= 1;
        }
        
        // all bits are used
        return -1;
    }

Where MAX_TOUCHES = 15, so it means cocos can handle 15 such corrupted touches , so after 15 game overs when touch end not sent to cocos - touches get disabled in game and user can’t click anything ;/ I think there is some wrong observer logic which check if touch still active or not and stack with touches should be cleaned in some way.

i think it’s related to a similar problem i had. i broke it down to very few lines here:

http://discuss.cocos2d-x.org/t/v3-15-1-button-stops-working-after-removechild-and-addchild

if you checkout the previous version v3.15, it should work normal.

Seems like a serious issue, @zhangxm can you take a look?

@energyy Yep, we depend on end signal to know which finger is up. I think the problem is that, the end touch message is not sent.

Is there way to force send end signal or safely clean touches stack? otherwise touches stack get full with corrupted touches without touch end and touches get disabled in game when it gets full.

You can invoke Cocos2dxGLSurfaceView.onTouchEvent() and pass end signal, but i don’t think it is a good idea since this function is callback by system, and you make break things if not invoke it correctly. As i said above, why the end signal is missed?

You holding finger on screen and at this time Interstitial ad appear, you remove finger from screen but cocos2d-x still see like u holding finger on screen. If this happens 15 times, cocos2d-x thinking that there is 15t fingers on screen and after that it stoping to accept any touch. There is no way to send to cocos that finger is not on screen and it should remove from stack this event.

Yep, it seems a problem. May be we lost some message, but i have not idea where to remove the finger.

@energyy did you find any fix for this issue?

I am reopening this ticket since I have the same issue. Anyone found a fix for this?

@ebouchard is there a GitHub issue for this? If not, please create one and take me. Thank you.

2 issues mentioned here - including this one

@kerryk - thank you.

I think engine will receive touch cancel events at this time. We can remove the limitation of max touches number, but it will cause memory leak if touch objects are not deleted.

I dont think the problem is MAX_TOUCHES. In our examples, touches remain stuck in general without reaching the MAX_TOUCHES limit.

Could somebody provide a demo?

So, I investigated a lot this issue today. I still do not know the root cause of why the onTouchEnded or Cancelled is never called, but I was able to get a fix. I created a new function in CCGLView to cancel all pending touch events. I don’t know if this the proper way of doing it, but it has been tested on both Android and IOS and it works like a charm. I just call it as soon as I show an ad. That way I make sure nothing is active when coming back from the ad.:

void GLView::cancelAllTouchesEvent()
{
	EventTouch touchEvent;
	intptr_t id = 0;
	float x = 0.0f;
	float y = 0.0f;

	std::map<intptr_t, int>::iterator it = g_touchIdReorderMap.begin();
	while (it != g_touchIdReorderMap.end())
	{
		auto iter = it;
		/* Add to the set to send to the director */
		Touch* touch = g_touches[iter->second];
		if (touch)
		{
			CCLOGINFO("Ending touches with id: %d, x=%f, y=%f", (int)id, x, y);
			touch->setTouchInfo(iter->second, (x - _viewPortRect.origin.x) / _scaleX,
				(y - _viewPortRect.origin.y) / _scaleY);

			touchEvent._touches.push_back(touch);

			g_touches[iter->second] = nullptr;
			removeUsedIndexBit(iter->second);

			it = g_touchIdReorderMap.erase(it);
		}
		else
			++it;
	}

	if (touchEvent._touches.size() == 0)
	{
		CCLOG("touchesEnded or touchesCancel: size = 0");
		return;
	}

	touchEvent._eventCode = EventTouch::EventCode::CANCELLED;
	auto dispatcher = Director::getInstance()->getEventDispatcher();
	dispatcher->dispatchEvent(&touchEvent);

	for (auto& touch : touchEvent._touches)
	{
		// release the touch object.
		touch->release();
	}
}

Interesting approach. Not an actual fix for us to call this manually, but seems that it should do the trick -very nice )

What i did in my case that can also help:

  1. Let’s assume user wins. There is win animation for 3 seconds then i will show ad.
  2. I check before hand if ad is cached.
  3. I will disable user touches for 4 seconds.
  4. So user makes his last touch, he wins, i disable touches, show 3 seconds animation, then interstitial. In case interstitial faills to show, touches will anw be enabled again in the next second.

But again mine is a workaround ) Can you try to call this code of yours in RootViewController.mm instead of manually? This method is usually called befored an ad is shown.

-( void ) viewWillDisappear:( BOOL ) animated {