Codea Modern API Extension Library - Manage Craft/Legacy backwards compatibility and customise almost any Lua APIs including userdata

Also hang on, did you sneak in Craft’s entity:add back into Modern in the latest build @sim :open_mouth: ? Awesome entity:add seems to work in a one liner again without my dependency thank you :grin:

1 Like

This is how the _obj model for the spaceship seems to appear in editor for me with being autocompleted to spaceCraft3_obj and will appear this way when selected from the asset viewer too.

@sim Here’s another horribly compressed gif screen recording of this time pressing the “Generate” button in EverLancer. Codea’s UI is frozen after doing this despite it seemingly still running, this is what I mean by the Tasks code is somehow interfering with Codea’s main thread somewhere. The only way out seems to be restarting the editor, which then shows a “report crash” notification from TestFlight.

image

Gosh that looks cool I wish I could run it

@UberGoober It might be possible to make it run if the “World Generator” example runs for you. I started this project duplicating by the example directly with Codea, so it might work if you start from there then replace the tabs. Do you have different asset names in your “World Generator” example somehow if this works for you? As this is where all the assets except the spaceship are from otherwise (although there doesn’t appear to be any ‘in project’ assets when I check in my version of the project).

World Generator Runs.zip (2.1 MB)

I had to tweak the original world generator because it crashes Codea on my iPhone 16e. I forget exactly what I did but it was something like lowering the texture resolution.

In any case this version runs just fine on my phone.

@UberGoober I think I have fixed the problem that was stopping the initial planet generation working.

The update function hook for ‘Tasks’ in CraftExtensions needed altering although the generate button still freezes Codeas UI.

    _G['update'] = extensions.hook(_G['update'],function(base,...)
        Tasks.update()
        base(...)
    end)

Let me know how you get on with this version :slightly_smiling_face:

EverLancer2.zip (2.2 MB)

1 Like

This one works and looks very nice thank you. Oh it put this message out of order! Weird.

1 Like

@UberGoober @sim Just found that it is something about the print statements from Tasks.debug=true causing Codea to freeze/crash when pressing the generate button. So a fix for now is just commenting out Tasks.debug = true from the Generate button’s callback.

    parameter.action("Generate", function()
        --Tasks.debug = true
        generate() 
    end)
1 Like

Sorry to report it but that doesn’t affect the crashing.

Haha don’t worry I almost wasn’t expecting it to be that simple…

Here’s another possible fix/slight difference in my version, that I’ve been experimenting with too, in CraftExtensions in the Tasks tab, swap the update hook to:

    _G['update'] = extensions.hook(_G['update'],function(base,...)
        self.mainThread = coroutine.running()
        Tasks.update()
        base(...)
    end)

Although for some reason for me it seems to work whether I have self.mainThread = coroutine.running() or not as long as I have a Tasks.debug = true commented out too :confused: . Does feel suspiciously timing oriented, which is definitely possible, whether it’s my simulated ‘threads’ / Tasks misbehaving between each other or something else I am not sure.

There is a consideration for certain drawing functions that must be called from draw/update, as in Codea’s main thread, as opposed to in the ‘background’ / inside coroutines which will cause a crash too however. Which is difficult to trace, it’s entirely possible there’s some circumstances I have not accounted for occurring too :man_facepalming:.

I could do with a way of getting a traceback in this circumstance, when I’ve broken Codea itself somehow and caused it to crash. I don’t know if this is possible though? ( also @sim )

You can write it yourself and you can to some extend use pcall and stacktrace. To write it yourself you have to save the log to local settings so you can retrieve it after the app relaunches.

Yeah I have tried this too, the problem on my side is there’s no unexpected stops in the trace etc and rendering keeps going afterwards. So really I think I might need Codea’s trace from an Objective-C / Xcode point of view or a debugger which I can breakpoint it all over with :man_facepalming:

Several people (including me!) have posted different debuggers with different abilities on the forums. I think @Steppers even made one that’s on WebRepo. I would not use mine, it’s somewhat lame, but the others might be helpful.

Kind of a project night cobra thing right there too lol

1 Like

Ohh interesting I’ve have not seen these, I shall have to give them a go and/or extend them :grin:

Assuming I did this the way you intended:

Tasks = class()
Tasks.debug = false
Tasks.all = {}
Tasks.update = function()
    for tasks in pairs(Tasks.all) do
        tasks:resume()
    end
end

TaskInvoker = class()
function TaskInvoker:init(func,...)
    self.func = func
    self.args = {...}
end

function TaskInvoker:invoke()
    return {self.func(table.unpack(self.args))}
end

TaskSleeper = class()
function TaskSleeper:init(timeout)
    self.timeout = timeout or 0
end

function Tasks:init(entity)
    self.tasks = {}
    self.limit = 0.75
    Tasks.all[self]=self
end

function Tasks:run(func, args, callback)
    if type(args) == "function" then
        callback = args
        args = nil
    end
    local task = {
        thread = coroutine.create(function(...)
            --coroutine.yield()
            return func(...) 
        end),
        args = args or {},
        callback = callback,
        clock = 0
    }
    self.tasks[task.thread] = task
    if Tasks.debug then
        print(task.thread," - run")
    end
end

function Tasks:clock()
    return os.clock()
end

function Tasks:resume()
    --self.mainThread = coroutine.running()
    local startTime = self:clock()
    local maxTime = self:clock() + self.limit
    local removeTasks = {}
    local resumeTasks = {}
    for thread, task in pairs(self.tasks) do
        if startTime > task.clock then
            resumeTasks[thread] = task
        end
    end
    for thread, task in pairs(resumeTasks) do
        local threadStatus = coroutine.status(thread)
        local results = {}
        local resultsStatus = false
        local args = task.args or {}
        if threadStatus ~= "dead" then
            if Tasks.debug then
                print(thread," - resume")
            end
            results = {coroutine.resume(thread, table.unpack(args))}
            resultsStatus = results[1]
            table.remove(results,1)
            local result = results[1]
            if result and type(result) == "table" and result.is_a then
                if result:is_a(TaskInvoker) then
                    if Tasks.debug then
                        print(thread, " - invoke")
                    end
                    local invoker = result
                    results = invoker:invoke()
                elseif result:is_a(TaskSleeper) then
                    if Tasks.debug then
                        print(thread," - sleep")
                    end
                    task.clock = self:clock() + result.timeout
                end
            end
            assert(type(task) == "table", tostring(task))
            if Tasks.debug then
                print(thread," - results")
                for k,v in pairs(results) do
                    print(k,"= ",v)
                end
            end
            task.args = results
            threadStatus = coroutine.status(thread)
        end
        if threadStatus == "dead" then
            if Tasks.debug then
                print(thread, " - callback")
            end
            local callback = task.callback
            if callback then
                callback(table.unpack(task.args))
            end
            table.insert(removeTasks, thread)
        end
        if self:clock() >= maxTime then
            break
        end
    end
    for i,thread in ipairs(removeTasks) do
        if Tasks.debug then
            print(thread, " - dead")
        end
        self.tasks[thread]=nil
    end
end

function Tasks:running()
    local thread = coroutine.running()
    return self.tasks[thread]
end

function Tasks:destroy()
    Tasks.all[self] = nil
end

Task = class()

function Task:init(func,args,callback)
    self.func = func
    self.args = args or {}
    self.callback = callback
end

function Task:execute()
    return self.func(table.unpack(self.args))
end

TasksExtension = class()
function TasksExtension:init()

end

function TasksExtension:setup()
    Tasks.pool = Tasks()
    self.mainThread = coroutine.running()
    Task.yield = function(...)
        local thread = coroutine.running()
        if thread ~= self.mainThread then
            if Tasks.debug then
                print(thread," - yield")
            end
            return coroutine.yield(...)
        end
        return ...
    end
    Task.sleep = function(timeout)
        return Task.yield(TaskSleeper(timeout))
    end
    Task.invoke = function(func, ...)
        local thread = coroutine.running()
        if thread ~= self.mainThread then
            if Tasks.debug then
                print(thread," - invoke ",func,...)
            end
            return Task.yield(TaskInvoker(func,...))
        end
        return func(...)
    end
    Task.run = function(task, args, callback)
        if type(args) == "function" then
            callback = args
            args = nil
        end
        args = args or {}
        if type(task) == "function" then
            local func = task
            task = Task(func, args, callback)
        end
        assert(type(task) == "table" and task.is_a and task:is_a(Task))
        local taskFunc = function() return Task.invoke() end
        Tasks.pool:run(task.execute, {task}, task.callback)
        return task
    end
    _G['update'] = extensions.hook(_G['update'],function(base,...)
        self.mainThread = coroutine.running()
        Tasks.update()
        base(...)
        end)
end

extensions.register(TasksExtension())

…and didn’t mess anything else up, I’m still seeing the crash.

This should go on WebRepo if you’re willing.

I am definitely willing to, I was holding off for now while I try fix the project crashing Codea :sweat_smile:

It’s working for me now

Yeah it does this haha I think I also know why though. It’s an out of memory issue due to my handling of textures during generation and scaling Codea does in the background per device interacting with each other undesirably. Which @jfperusse kindly dug into and explained what was happening to Codea to cause the crashes. I was intending to optimise this anyway as it’s necessary to squeeze as much memory as possible for something like what I plan with it for planetary landings with voxels. Once I can get my head round the theory around how more exactly :man_facepalming::joy: