Need more context: Lua Runtime Error: lua_pcall failed with status: 2 - -1: attempt to call a table value

On the simulator, I’m getting a very unconsistent error: sometimes it crashes the simulator, sometimes it does not. Sometimes I can keep using the app while the error keeps showing up in the console, sometimes the app freezes and I have to restart it. The only error message I’ve got is :

Lua Runtime Error: lua_pcall failed with status: 2, error message is: D:\a\corona\corona\platform\resources\init.lua:-1: attempt to call a table value

And that’s basically it. No more informations, I have almost no idea what’s causing it. From the test i’ve done, it seems related to some touch event but I really can’t tell as it’s so random.

Any clue?

I had a quick look in the Solar2d source code, and that file platform\resources\init.lua has a few pcalls.

The most likely calls to cause this problem (based on what you’ve said) are in
function EventDispatcher:dispatchEvent( event ) which I think handles all event dispatching, which would include touch events.

Do you ever add event listeners by attaching them to a table and passing the table as the argument to addEventListener? i.e. this:

local myObject = {}

function myObject:touch(event)
	--do touch stuff
end

myObject:addEventListener("touch", myObject)

rather than this:

local myObject = {}

local function standaloneTouchFunction(event)
	--do touch stuff
end

myObject:addEventListener("touch", standaloneTouchFunction)

If so, my best guess is that somehow the touch function is getting lost and so a table with no touch function is being passed into the addEventListener call. But it is just a guess.

1 Like

Thank you for your this lead. I’m gonna try to look into it further!

In the meantime, I’ve tried to reduce the problematic code to its bare minimum in order to reproduce the issue and locate its origin. It’s slightly different from my original project but it behaves exactly the same.

If anyone wanna take a look, here’s a zip file: Solar2D-TouchIssue.zip (5.1 KB)
The source code will also be added at the end of this post.

What I’m basically doing :

  • I have a button that is inserted into a group with createButtons(). This button (and every other button created after) is added to a buttonList.

  • A gaussion blur filter is applied to the whole group (render.lua) with applyShaderEffect(). Since it’s using graphics.newTexture, touch events can’t work properly.

  • A touch overlay is being created with createTouchZone(): it receives every touch inputs and, after scanning the buttonList based on the coordinates of each button, it redispatches each event accordingly.

It works… Well, almost. It not consistent and seems pretty random, but after interacting with the button, the console randomly throws the “Lua Runtime Error: lua_pcall failed with status: 2, error message is: D:\a\corona\corona\platform\resources\init.lua:-1: attempt to call a table value” error.

Based on what you’ve described and from what my own investigations, the closest thing that may be related to the issue revolves around main.lua at line 191 :

target:dispatchEvent(event)

But I’m not sure how to deal with it.

Here’s a video of my issue : https://streamable.com/dzs109

The safest way to reproduce the bug is to click and hold on the white rectangle, move the cursor and release. It’s not consistant and multiples tries -and even relaunch of the simulator- may be needed to reproduce the error.

All of this has been made with the help of @Kan98 that I’m allowing myself to mention if by any chances he has time to take a look!

MAIN.LUA

local _common = require("common")
local screenOriginX = math.abs(display.screenOriginX)
local screenOriginY = math.abs(display.screenOriginY)
local realWidth = (display.contentWidth+screenOriginX*2)
local realHeight = (display.contentHeight+screenOriginY*2)
local _W = realWidth*0.5
local _H = realHeight*0.5
---------------------------------------------------------------------------------------------------------------
-- GENERAL GROUP
---------------------------------------------------------------------------------------------------------------
_common.generalGroup = display.newGroup()
_common.generalGroup.x = _W
_common.generalGroup.y = _H
---------------------------------------------------------------------------------------------------------------
--	BUTTON CREATION
---------------------------------------------------------------------------------------------------------------
local buttonList = {}
function createButtons()

	-- Button creation
	local button = display.newRect(0,0,200,200)
	_common.generalGroup:insert(button)
	button.name = "testButton"

	-- Button touch listener (basically does nothing)
	local function touchListener(event)

		print("touchListener() - event.phase = " .. event.phase)
		if event.phase == "began" then

			display.getCurrentStage():setFocus(event.target)
			return true

		elseif event.phase == "moved" then

		elseif event.phase == "ended" or event.phase == "cancelled" then

			display.getCurrentStage():setFocus(setFocus)
			return true

		end

	end
	button:addEventListener("touch", touchListener)

	-- Adding the button to the buttonList
	local slot = #buttonList + 1
	buttonList[slot] = {}
	buttonList[slot].obj = button
	buttonList[slot].objId = slot

end
---------------------------------------------------------------------------------------------------------------
-- GET ROTATED RECTANGLE BOUNDS
---------------------------------------------------------------------------------------------------------------
local function getRotatedRectangleBounds(centerX, centerY, width, height, rotationAngle)

	-- Convert angle to radians
	local angle = math.rad(rotationAngle)
	local cos = math.cos(angle)
	local sin = math.sin(angle)
	
	-- Calculate half dimensions
	local halfW = width / 2
	local halfH = height / 2
	
	-- Calculate the four corners relative to center (before rotation)
	local corners = {
		{x = -halfW, y = -halfH},
		{x =  halfW, y = -halfH},
		{x =  halfW, y =  halfH},
		{x = -halfW, y =  halfH}
	}
	
	-- Rotate each corner and find min/max values
	local xMin, xMax, yMin, yMax
	
	for i, corner in ipairs(corners) do

		-- Apply rotation
		local x = centerX + corner.x * cos - corner.y * sin
		local y = centerY + corner.x * sin + corner.y * cos
		
		-- Initialize or compare
		if i == 1 then
			xMin, xMax = x, x
			yMin, yMax = y, y
		else
			xMin = math.min(xMin, x)
			xMax = math.max(xMax, x)
			yMin = math.min(yMin, y)
			yMax = math.max(yMax, y)
		end
	end
	
	return xMin, xMax, yMin, yMax

end

---------------------------------------------------------------------------------------------------------------
-- VERIFY IF INBOUNDS
---------------------------------------------------------------------------------------------------------------
local function inBounds(event, obj, order)

	if not event or not event.x or not event.y then
		return false
	end

	local ex = _common.mouseX
	local ey = _common.mouseY

	local bounds = obj.contentBounds or {}

	local objRealX, objRealY = obj:localToContent(0,0)
	local xMin, xMax, yMin, yMax = getRotatedRectangleBounds(objRealX, objRealY, obj.width, obj.height, obj.rotation)

	local coordComparisonstring = xMin .. " < " .. _common.mouseX .. " < " .. xMax .. " - " .. yMin .. " < " .. _common.mouseY .. " < " .. yMax

	if bounds then

		if ex < xMin or ex > xMax or ey < yMin or ey > yMax then

			--print(coordComparisonstring .. " : OUTSIDE OBJ - " .. obj.name)
			return false

		else
			
			--print(coordComparisonstring .. " : INSIDE OBJ - " .. obj.name)

			local bounds = {
				xMin = xMin,
				xMax = xMax,
				yMin = yMin,
				yMax = yMax,
			}
			return true, bounds

		end

		return false

	end

end
---------------------------------------------------------------------------------------------------------------
-- TRANSPARENT OVERLAY THAT RECEIVES EVERY TOUCH EVENTS AND DISPATCHES THEM TO ANY OTHER BUTTON
---------------------------------------------------------------------------------------------------------------
function createTouchZone(details)

	-- touchZone
	touchZone = display.newRect(0,0,display.contentWidth+screenOriginX*2, display.contentHeight+screenOriginY*2)
	touchZone.anchorX = 0
	touchZone.anchorY = 0
	touchZone.isHitTestable = true
	touchZone:setFillColor(1,0,1,0.0)


	-- touchListener
	local function touchObjectListener(event)

		_common.mouseX = event.x
		_common.mouseY = event.y

		-------------------------------------------------------------------------------------------------------
		-- REDISPATCHINGH EVENT
		-------------------------------------------------------------------------------------------------------

		--print("redispatchEvent()")

		local objs = {}

		-- Find "touchables"
		for i=#buttonList, 1, -1 do

			local obj = buttonList[i].obj
			local isInBounds
			isInBounds, obj.bounds = inBounds(event, obj, i)
			if isInBounds then
				objs[#objs + 1] = obj
			end

		end

		for i=1, #objs do
			
			local target = objs[i]
			event.target = target
			local handled = false
			if target.dispatchEvent then

				handled = target:dispatchEvent(event)

			end

			if handled then

				--print("redispatchEvent() - event.phase:" .. string.upper(event.phase) .. " - " .. target.name)
				--print("1) name:" .. target.name .. " - phase:" .. event.phase .. " - _common.mouseX,_common.mouseY: " .. _common.mouseX, _common.mouseY .. " || " .. realXMin, realXMax, realYMin, realYMax)

				return target
			end


		end

		return true
			
	end
	touchZone:addEventListener("touch", touchObjectListener)

	-- Keep the touchZone in front
	local function movetoFront(event)
		touchZone:toFront()
	end
	Runtime:addEventListener("enterFrame", movetoFront)

end
---------------------------------------------------------------------------------------------------------------
-- APPLYING THE BLUR SHADER
---------------------------------------------------------------------------------------------------------------
function applyShaderEffect()

	require("render")

end
---------------------------------------------------------------------------------------------------------------
-- START
---------------------------------------------------------------------------------------------------------------
createButtons()
applyShaderEffect()
createTouchZone()

RENDER.LUA

local _common = require("common")
local screenOriginX = math.abs(display.screenOriginX)
local screenOriginY = math.abs(display.screenOriginY)
local realWidth = (display.contentWidth+screenOriginX*2)
local realHeight = (display.contentHeight+screenOriginY*2)
local _W = realWidth*0.5
local _H = realHeight*0.5
local stage = display.getCurrentStage()
local render, view
local group = _common.generalGroup
local setFocus = stage.setFocus
local focusedObj
-------------------------------------------------------------------------------------------------------
-- RESIZE
-------------------------------------------------------------------------------------------------------
local function resize()


	local aw = realWidth
	local ah = realHeight
	local width = aw
	local height = ah
	local centerX = aw*0.5
	local centerY = ah*0.5

	display.remove(render)
	render = graphics.newTexture({type="canvas", width=aw, height=ah})
	render.anchorX = -(centerX / aw)
	render.anchorY = -(centerY / ah)

	display.remove(view)
	view = display.newImageRect(render.filename, render.baseDir, width, height)

	view.anchorX = 0
	view.anchorY = 0
	view.x = 0
	view.y = 0
	view.fill.effect = "filter.blurGaussian" -- your custom Filter Effect goes here
	view.fill.effect.horizontal.blurSize = 256
	view.fill.effect.horizontal.sigma = 8
	view.fill.effect.vertical.blurSize = 256
	view.fill.effect.vertical.sigma = 8
	view._isRenderer = true

	_common.view = view
	
end

-------------------------------------------------------------------------------------------------------
-- ENTERFRAME
-------------------------------------------------------------------------------------------------------
local function enterFrame(event)

	render:invalidate("cache")

	-- add anything new to the render queue
	for i = 1, _common.generalGroup.numChildren do

		local child = _common.generalGroup[i]

		if child and not child._isRenderer then
			group:insert(child)
		end

	end

	-- render!
	render:draw(group)
	render:invalidate("cache")
	
end

-------------------------------------------------------------------------------------------------------
-- SHOW/HIDE EFFECT
-------------------------------------------------------------------------------------------------------
local effect = true
local function key(event)

	local phase = event.phase
	local name = event.keyName

	if phase == "up" then

		if name == "f2" then

			effect = not effect
			print("SWITCH CRT")
			if effect then

				view.fill.effect = "filter.custom.crt"

			else

				view.fill.effect = nil

			end

		end

	end

end

-- Events
group._isRenderer = true
resize()
Runtime:addEventListener("key", key)
Runtime:addEventListener("resize", resize)
Runtime:addEventListener("enterFrame", enterFrame)

COMMON.LUA

local _common = {}
return _common

Tried a bit, the problem is displayCurrentStage:setFocus, but not sure why. Hiding it seems to work fine.

	local function touchListener(event)
		print("touchListener() - event.phase = " .. event.phase)
		local target = event.target
		if not target then
			return true
		end
		if event.phase == "began" then
			display.getCurrentStage():setFocus(target)
			target.isFocus = true
		elseif target.isFocus then
			if event.phase == "moved" then

			elseif event.phase == "ended" or event.phase == "cancelled" then
				display.getCurrentStage():setFocus(nil)
				target.isFocus = false
			end
		end	
		return true
	end

Try this.

It doesn’t seem to work very well either, but the exact cause is that the touch phase ended isn’t dispatched so setFocus isn’t clear.
Also, the current click event is actually simulated from the Render Texture’s click event, so there’s no need for currentStage:setFocus.

It seems like this is the mostly the case, but I just had this which seems to indicate the error got triggered right after/during the began phase (that was right after relaunching the whole project, so basically first interaction):

15:02:04.160  ABOUT TO DISPATCH EVENT.PHASE BEGAN
15:02:04.160  touchListener() - event.phase = began
15:02:05.174  Lua Runtime Error: lua_pcall failed with status: 2, error message is: D:\a\corona\corona\platform\resources\init.lua:-1: attempt to call a table value
15:02:05.174  touchListener() - event.phase = moved
15:02:05.174  touchListener() - event.phase = moved
15:02:05.190  Lua Runtime Error: lua_pcall failed with status: 2, error message is: D:\a\corona\corona\platform\resources\init.lua:-1: attempt to call a table value

Just some guess, but could it be related to the enterFrame function in the render.lua file?
Correct me if i’m wrong, but it constantly adds the button back to _common.generalGroup.

Could it have any conflict with how Solar2D deals with dispatched events?

The errors happens less frequently than in my main project, which has way more buttons. Could it be related to a conflict between the time needed to calculate a new frame after adding every button to the main group vs the time needed for the touch event to get taken in account? (again, i’m just taking a guess)

	local function touchListener(event)
		print("touchListener() - event.phase = " .. event.phase)
		local target = event.target
		if not target then
			return true
		end
		if event.phase == "began" then
			-- display.getCurrentStage():setFocus(target)
			touchZone.focus = target
		elseif touchZone.focus then
			if event.phase == "moved" then

			elseif event.phase == "ended" or event.phase == "cancelled" then
				-- display.getCurrentStage():setFocus(nil)
				touchZone.focus = nil
			end
		end
		return true
	end
local function touchObjectListener(event)
		if event then
			_common.mouseX = event.x
			_common.mouseY = event.y

			-------------------------------------------------------------------------------------------------------
			-- REDISPATCHINGH EVENT
			-------------------------------------------------------------------------------------------------------

			--print("redispatchEvent()")
			if event.phase == "moved" then
				if touchZone.focus then
					event.target = touchZone.focus
					touchZone.focus:dispatchEvent(event)
				end
				return true
			end

			if event.phase == "ended" or event.phase == "cancelled" then
				if touchZone.focus then
					event.target = touchZone.focus
					touchZone.focus:dispatchEvent(event)
					touchZone.focus = nil
					return true
				end
			end
			local objs = {}

			-- Find "touchables"
			for i = #buttonList, 1, -1 do
				local obj = buttonList[i].obj
				local isInBounds
				isInBounds, obj.bounds = inBounds(event, obj, i)
				if isInBounds then
					objs[#objs + 1] = obj
				end
			end

			for i = 1, #objs do
				local target = objs[i]

				event.target = target
				local handled = false
				if target.dispatchEvent then
					target:dispatchEvent(event)
				end
			end
		end
		return true
	end

I made some change to remove currentStage:setFocus, currently it works well for me.

1 Like

Huge thanks!

I’ve implemented your changes to my main project. I have to do more testing and some adjustments as several touch events can sometimes behave weirdly but so far, so good: those errors seem to have disappear!

I’m not really sure I understand why removing the display.getCurrentStage:setfocus() from every button works though.