diff --git a/core/src/objects/script.cpp b/core/src/objects/script.cpp index bb10697..a6dc488 100644 --- a/core/src/objects/script.cpp +++ b/core/src/objects/script.cpp @@ -1,4 +1,5 @@ #include "script.h" +#include "common.h" #include "logger.h" #include "objects/base/instance.h" #include "objects/base/member.h" @@ -7,17 +8,23 @@ #include "objects/datamodel.h" #include "datatypes/ref.h" #include "lua.h" +#include #include +#include + +int script_wait(lua_State*); Script::Script(): Instance(&TYPE) { - source = "print \"Hello, world!\""; + source = "print \"Hello, world!\"\nwait(1)print \"Wait success! :D\""; } Script::~Script() { } void Script::Run() { - lua_State* L = dataModel().value()->GetService()->state; + std::shared_ptr scriptContext = dataModel().value()->GetService(); + + lua_State* L = scriptContext->state; // Create thread this->thread = lua_newthread(L); @@ -34,6 +41,11 @@ void Script::Run() { Data::InstanceRef(dataModel().value()->GetService()).PushLuaValue(Lt); lua_rawset(Lt, -3); + lua_pushstring(Lt, "wait"); + lua_pushlightuserdata(Lt, scriptContext.get()); + lua_pushcclosure(Lt, script_wait, 1); + lua_rawset(Lt, -3); + lua_pop(Lt, 1); // _G // Load source and push onto thread stack as function ptr @@ -50,4 +62,14 @@ void Script::Run() { void Script::Stop() { // TODO: +} + +int script_wait(lua_State* L) { + ScriptContext* scriptContext = (ScriptContext*)lua_touserdata(L, lua_upvalueindex(1)); + float secs = luaL_checknumber(L, 1); + + scriptContext->PushThreadSleep(L, secs); + + // Yield + return lua_yield(L, 0); } \ No newline at end of file diff --git a/core/src/objects/script/scriptcontext.cpp b/core/src/objects/script/scriptcontext.cpp index aa03220..1657dc6 100644 --- a/core/src/objects/script/scriptcontext.cpp +++ b/core/src/objects/script/scriptcontext.cpp @@ -1,12 +1,14 @@ #include "scriptcontext.h" #include "datatypes/cframe.h" -#include "datatypes/meta.h" +#include "datatypes/color3.h" #include "datatypes/vector.h" #include "logger.h" +#include "timeutil.h" #include -#include +#include +#include #include -#include +#include "lua.h" static int g_print(lua_State*); static int g_require(lua_State*); @@ -62,8 +64,70 @@ void ScriptContext::InitService() { lua_rawset(state, -3); } - lua_pop(state, 1); + lua_pop(state, 1); // _G + lua_getregistry(state); + + lua_pushstring(state, "__sleepingThreads"); + lua_newtable(state); + lua_rawset(state, -3); + + lua_pop(state, -1); // registry +} + +void ScriptContext::PushThreadSleep(lua_State* thread, float delay) { + // A thread is allowed to sleep multiple times at once, though this is a very edge-case scenario + + SleepingThread sleep; + sleep.thread = thread; + sleep.timeYieldedWhen = tu_clock_micros(); + sleep.targetTimeMicros = tu_clock_micros() + delay * 1'000'000; + + sleepingThreads.push_back(sleep); + + // Add to registry so it doesn't get GC'd + + // https://stackoverflow.com/a/17138663/16255372 + lua_getregistry(state); // registry + lua_pushstring(state, "__sleepingThreads"); + lua_rawget(state, -2); // table + + lua_pushthread(thread); // key + lua_xmove(thread, state, 1); + lua_pushboolean(state, true); // value + lua_rawset(state, -3); // set + + lua_pop(state, 2); // pop table and registry +} + +void ScriptContext::RunSleepingThreads() { + for (int i = 0; i < sleepingThreads.size();) { + bool deleted = false; + + SleepingThread sleep = sleepingThreads[i]; + if (tu_clock_micros() >= sleep.targetTimeMicros) { + // Time args + lua_pushnumber(sleep.thread, float(tu_clock_micros() - sleep.timeYieldedWhen) / 1'000'000); + lua_pushnumber(sleep.thread, float(tu_clock_micros()) / 1'000'000); + lua_resume(sleep.thread, 2); + + // Remove thread + sleepingThreads.erase(sleepingThreads.begin() + i); + + // Erase from registry + lua_getregistry(state); // registry + lua_pushstring(state, "__sleepingThreads"); + lua_rawget(state, -2); // table + + lua_pushthread(sleep.thread); // key + lua_xmove(sleep.thread, state, 1); + lua_pushnil(state); + lua_rawset(state, -3); // set + } + + if (!deleted) + i++; + } } // https://www.lua.org/source/5.1/lbaselib.c.html diff --git a/core/src/objects/script/scriptcontext.h b/core/src/objects/script/scriptcontext.h index b197fb5..f3305a3 100644 --- a/core/src/objects/script/scriptcontext.h +++ b/core/src/objects/script/scriptcontext.h @@ -3,9 +3,19 @@ #include "objects/annotation.h" #include "objects/base/service.h" #include "lua.h" +#include + +struct SleepingThread { + lua_State* thread; + uint64_t timeYieldedWhen; + uint64_t targetTimeMicros; + bool active = true; +}; class DEF_INST_SERVICE ScriptContext : public Service { AUTOGEN_PREAMBLE + + std::vector sleepingThreads; protected: void InitService() override; bool initialized = false; @@ -15,6 +25,8 @@ public: ~ScriptContext(); lua_State* state; + void PushThreadSleep(lua_State* thread, float delay); + void RunSleepingThreads(); static inline std::shared_ptr Create() { return std::make_shared(); }; }; \ No newline at end of file diff --git a/core/src/timeutil.cpp b/core/src/timeutil.cpp new file mode 100644 index 0000000..1f1a326 --- /dev/null +++ b/core/src/timeutil.cpp @@ -0,0 +1,12 @@ +#include "timeutil.h" + +#include + +tu_time_t TIME_STARTED_MICROS = std::chrono::time_point_cast(std::chrono::high_resolution_clock::now()).time_since_epoch().count(); + + +tu_time_t tu_clock_micros() { + tu_time_t now = std::chrono::time_point_cast(std::chrono::high_resolution_clock::now()).time_since_epoch().count();; + + return now - TIME_STARTED_MICROS; +} \ No newline at end of file diff --git a/core/src/timeutil.h b/core/src/timeutil.h new file mode 100644 index 0000000..44476c5 --- /dev/null +++ b/core/src/timeutil.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +typedef uint64_t tu_time_t; + +// Provides a high-accuracy time since the program started in microseconds (via std::chrono) +tu_time_t tu_clock_micros(); \ No newline at end of file diff --git a/editor/placedocument.cpp b/editor/placedocument.cpp index be6cc53..2c7c629 100644 --- a/editor/placedocument.cpp +++ b/editor/placedocument.cpp @@ -4,6 +4,7 @@ #include "mainwindow.h" #include "objects/joint/snap.h" #include "objects/script.h" +#include "objects/script/scriptcontext.h" #include "rendering/surface.h" #include #include @@ -67,6 +68,7 @@ void PlaceDocument::timerEvent(QTimerEvent* evt) { gWorkspace()->PhysicsStep(deltaTime); placeWidget->repaint(); placeWidget->updateCycle(); + gDataModel->GetService()->RunSleepingThreads(); }