feat(lua): implemented coroutines for scripts + wait()
This commit is contained in:
parent
26459c9275
commit
13cad8e01a
6 changed files with 126 additions and 6 deletions
|
@ -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 <luajit-2.1/lauxlib.h>
|
||||
#include <luajit-2.1/lua.h>
|
||||
#include <memory>
|
||||
|
||||
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<ScriptContext>()->state;
|
||||
std::shared_ptr<ScriptContext> scriptContext = dataModel().value()->GetService<ScriptContext>();
|
||||
|
||||
lua_State* L = scriptContext->state;
|
||||
|
||||
// Create thread
|
||||
this->thread = lua_newthread(L);
|
||||
|
@ -34,6 +41,11 @@ void Script::Run() {
|
|||
Data::InstanceRef(dataModel().value()->GetService<Workspace>()).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
|
||||
|
@ -51,3 +63,13 @@ 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);
|
||||
}
|
|
@ -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 <cstdint>
|
||||
#include <luajit-2.1/lauxlib.h>
|
||||
#include <ctime>
|
||||
#include <chrono>
|
||||
#include <luajit-2.1/lua.h>
|
||||
#include <luajit-2.1/lualib.h>
|
||||
#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
|
||||
|
|
|
@ -3,9 +3,19 @@
|
|||
#include "objects/annotation.h"
|
||||
#include "objects/base/service.h"
|
||||
#include "lua.h"
|
||||
#include <vector>
|
||||
|
||||
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<SleepingThread> 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<Instance> Create() { return std::make_shared<ScriptContext>(); };
|
||||
};
|
12
core/src/timeutil.cpp
Normal file
12
core/src/timeutil.cpp
Normal file
|
@ -0,0 +1,12 @@
|
|||
#include "timeutil.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
tu_time_t TIME_STARTED_MICROS = std::chrono::time_point_cast<std::chrono::microseconds>(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::microseconds>(std::chrono::high_resolution_clock::now()).time_since_epoch().count();;
|
||||
|
||||
return now - TIME_STARTED_MICROS;
|
||||
}
|
8
core/src/timeutil.h
Normal file
8
core/src/timeutil.h
Normal file
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
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();
|
|
@ -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 <cstdio>
|
||||
#include <memory>
|
||||
|
@ -67,6 +68,7 @@ void PlaceDocument::timerEvent(QTimerEvent* evt) {
|
|||
gWorkspace()->PhysicsStep(deltaTime);
|
||||
placeWidget->repaint();
|
||||
placeWidget->updateCycle();
|
||||
gDataModel->GetService<ScriptContext>()->RunSleepingThreads();
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue