I’m using Google Analytics, and when I checked it, there was the error mentioned in the title with no stack trace.
Anyone know any causes for that error? Should I just switch to spawn?
I think that it is caused by you trying to run coroutine.resume on a function on either an already running coroutine or a function that isn’t a coroutine
Oops, forgot to mention the only thing i use in the game related to coroutines is coroutine.wrap. Every coroutine.wrap is written like this:
coroutine.wrap(FUNCTION)()
nothing is written like this (a common problem I’ve seen in other posts):
local d = coroutine.wrap(FUNCTION)
d()
I haven’t used coroutine.wrap before so I am probably not the best person to be answering your question. However, with the wiki article I just read and prior experience with coroutines I think that
Coroutine.resume(coroutine.create(function))
Should work just as fine as coroutine.wrap()
Can you provide a basic version or full version of your code? Switching to spawn isn’t recommended.
I think this may be the problem function, I don’t really know. (simplified)
local function f()
while 1 do
m = m + 1
if m >= 2100 then break end
wait(1)
end
f()
end
coroutine.wrap(f)()
Does it immediately break or when “m” reaches 2100? Also, I have never tried calling a coroutines function inside the function.
That is basically the same as the first way you do it. Neither way will cause any issue by themselves.
Oops, yeah. I think in the other problems they did
local d = coroutine.wrap(FUNCTION)()
d()
Yes.
local f = coroutine.wrap(func)
is similar to doing
local f
do
local c = coroutine.create(func)
f = function(...) return coroutine.resume(c,...) end
end
my point is that the coroutine itself is only created once
coroutines are essentially threads and the objects themselves are of the thread datatype. how threads in lua work is they can yield and they can terminate (with or without returns). once a thread terminates you cannot revive it unless you create a new one. ‘suspended’ coroutines are yielded threads. this function might be more of what youre looking for:
local wrap = function(func)
return function(...)
return coroutine.resume(coroutine.create(func),...)
end
end
and a better implementation (without the extra call) would be:
local spawnf = function(func,...)
return coroutine.resume(coroutine.create(func),...)
end
Nothing in the code present definitely shows whats it causing the error. Maybe could we get the full error message? How long has the code been running for when this happens? Have you tried inserting calls to print to narrow down where it magically crashes? I don’t think you can use the Roblox debugger to see where the script execution stopped if a proper error message isn’t printed.
As @RedDuck765 said, I’ve only seen this when coroutine.resume is called on a coroutine when it is running. That said, it could be possible that a Roblox C++ function that yields could cause this when it tries to resume the coroutine that called it. I’d add a print before and after each external call as well.
In order to make sure that you catch every external call, you could use my sandbox module. It’ll capture everything your script does, and allow you to print everything so you can see the last state your script was in when it crashed.
Here is an edited version of it that prints every time you index a table/userdata that wasn’t created locally and whenever you call a function. If you see that the last print was a function call, then it likely caused the error. The tostring of it will be returned, so you need to follow it back to the last index print to find the name of it.
-- Localize global functions to prevent a changing environment
-- from messing with this code. This should allow multiple
-- instances of this code to run at once. (Don't do that...)
local setfenv = setfenv
local getfenv = getfenv
local setmetatable = setmetatable
local type = type
local select = select
local tostring = tostring
local newproxy = newproxy
-- Unfortunately metamethods have different ways of invoking
-- the default metamethods of values. This table provides
-- metamethods which does that, so they can be easily wrapped.
local Capsule = {}
Capsule.__metatable = 'This debug metatable is locked.'
function Capsule:__index(k)
print("Indexed", k, "returned", self[k])
return self[k]
end
function Capsule:__newindex(k, v) self[k] = v end
function Capsule:__call(...) self(...) end
function Capsule:__concat(v) return self .. v end
function Capsule:__unm() return -self end
function Capsule:__add(v) return self + v end
function Capsule:__sub(v) return self - v end
function Capsule:__mul(v) return self * v end
function Capsule:__div(v) return self / v end
function Capsule:__mod(v) return self % v end
function Capsule:__pow(v) return self ^ v end
function Capsule:__tostring() return tostring(self) end
function Capsule:__eq(v) return self == v end
function Capsule:__lt(v) return self < v end
function Capsule:__le(v) return self <= v end
function Capsule:__len() return #self end
-- We don't want to prevent interfaces and 'data' from being
-- garbage collected. While unwrapped 'data' shouldn't be present
-- in lua, interfaces will always be present in lua for the
-- lifetime of the data. So, we remove the data when the interfaces
-- are garbage collected, and allow interfaces to be collected.
local original = setmetatable({}, {__mode = 'k'})
local wrapper = setmetatable({}, {__mode = 'v'})
-- Since RBXLua isn't multithreaded and wrap/unwrap cannot
-- yield, i and n are fine being passed as upvalues. This
-- allows a very simple implmentation of a function wrapper
-- without the use of tables and unpack, which does not
-- always preserve nil values.
local i, n = 1, 0
-- This function is called to make sure no wrappers get passed
-- outside the sandbox. The only data that leaves the sandbox is
-- through metamethod and function call arguments. These arguments
-- are always unwrapped.
local function unwrap(...)
if i > n then
i = 1
n = select('#', ...)
-- make sure we handle n = 0 correctly, otherwise we can break
-- vararg functions like 'print()' because they see the number of
-- inputs, including nil ones.
if n == 0 then
return
end
end
-- value may be nil, we should proceed with caution.
local value = select(i, ...)
if value and wrapper[value] then
value = original[wrapper[value]]
end
-- wrap the other values too
i = i + 1
if i <= n then
return value, unwrap(...)
else
return value
end
end
-- The return value of all function and metamethod calls is wrapped.
local function wrap(...)
if i > n then
i = 1
n = select('#', ...)
-- make sure we handle n = 0 correctly, otherwise we can break
-- vararg functions like 'print()' because they see the number of
-- inputs, including nil ones.
if n == 0 then
return
end
end
-- value may be nil, we should proceed with caution.
local value = select(i, ...)
if value then
local wrapped = wrapper[value]
if not wrapped then
local vType = type(value)
if vType == 'function' then
local func = value -- value will be changed to the wrapped version soon.
wrapped = function(...)
print("calling", func)
return wrap(func(unwrap(...)))
end
elseif vType == 'table' then
wrapped = setmetatable({}, Capsule)
elseif vType == 'userdata' then
wrapped = setmetatable(newproxy(true), Capsule)
else
wrapped = value
end
wrapper[value] = wrapped -- Same data, same wrapper. Preserves equality tests.
wrapper[wrapped] = wrapped -- Prevent encapsulating capsules.
original[wrapped] = value -- store the original value to perform operations on and unwrap
end
value = wrapped
end
-- wrap the other values too
i = i + 1
if i <= n then
return value, wrap(...)
else
return value
end
end
-- Here, we ensure each metamethod is wrapped.
for key, metamethod in next, Capsule do
Capsule[key] = wrap(metamethod)
end
-- mwhaha, now even calling rawset is sandboxed...
-- the only functions not sandboxed are those included in the
-- locked string metatable (i.e. 'myStringVar:sub(i, j)')
return function()
return setfenv(2, wrap(getfenv(2))
end
And don’t worry, it only utilizes function environments, metatables, upvalues as arguments, recursive functions, varargs, and weak tables. What could possibly go wrong?
(Did I mention that this should be used for debugging purposes only?)
Edit: Note that if there is no function call right before then it must be a yielding Roblox function that is causing it. Also note that for this assumption to be true, you must require and call the module at the first line of every script you use. Like so: require(script.Parent.Sandbox)()
Side point: Please note that coroutine.resume(coroutine.create(function() ... end))
does NOT behave the same as coroutine.wrap(function() ... end)()
.
coroutine.resume
will run it in “protected mode”, thus errors will not show up but will be returned, whereas coroutine.wrap
does not run in protected mode. Robloxdev site documents this properly actually, but it’s commonly overlooked.
This doesn’t necessarily relate to the original problem, but thought it was worth mentioning just in case
The only error was the error from the title. I didn’t have a stack trace, server time, or anything to go along with it. Additionally, It very rarely happens(it has only happened one time) so I have no idea. The error doesn’t even make anything stop working, so I guess i will just ignore it.
it might be a coroutine within a coroutine that is erroring, hence the ghost error.
Oh, okay, I’ll look out for those. Thanks everyone for replying, I guess I’ll use this for a solution.