diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp index bef3b732717..9d0bee06934 100644 --- a/engines/twp/twp.cpp +++ b/engines/twp/twp.cpp @@ -64,7 +64,7 @@ Common::String TwpEngine::getGameId() const { Common::Error TwpEngine::run() { Graphics::PixelFormat fmt(4, 8, 8, 8, 8, 0, 8, 16, 24); initGraphics(1280, 720, &fmt); - _screen = new Graphics::Screen(); + _screen = new Graphics::Screen(1280, 720, fmt); XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0xAD}; // XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0x6D}; @@ -74,36 +74,39 @@ Common::Error TwpEngine::run() { Common::File f; f.open("ThimbleweedPark.ggpack1"); - GGPackDecoder pack; - pack.open(&f, key); + Scene scene; + scene.pack.open(&f, key); - GGPackEntryReader r; - r.open(pack, "RaySheet.png"); + const SQChar *code = R"( + function bounceImage() { + local image = createObject("RaySheet", ["fstand_head1"]); + local x = random(100, 1180); + local y = random(100, 620); - Image::PNGDecoder d; - d.loadStream(r); - const Graphics::Surface *surface = d.getSurface(); + do { + local steps = random(100, 150); + local end_x = random(0, 1180); + local end_y = random(0, 620); - GGPackEntryReader r2; - r2.open(pack, "RaySheet.json"); - Common::Array data(r2.size()); - r2.read(&data[0], r2.size()); - Common::String s(&data[0], r2.size()); + local dx = (end_x - x) / steps; + local dy = (end_y - y) / steps; - Common::JSONValue *jRay = Common::JSON::parse(s.c_str()); - const Common::JSONObject &jRayObj = jRay->asObject(); - const Common::JSONObject &jFrames = jRayObj["frames"]->asObject(); - const Common::JSONObject &jFrame = jFrames["bstand_body1"]->asObject(); - const Common::JSONObject &jRect = jFrame["frame"]->asObject(); + for (local i = 0; i < steps; i++) { + x += dx; + y += dy; + objectAt(image, x, y); + breaktime(0.01); + } + } while (1); + } - int x = (int)jRect["x"]->asIntegerNumber(); - int y = (int)jRect["y"]->asIntegerNumber(); - int w = (int)jRect["w"]->asIntegerNumber(); - int h = (int)jRect["h"]->asIntegerNumber(); - Common::Rect rect(x, y, x + w, y + h); + for (local i = 1; i <= 100; i++) { + startthread(bounceImage); + })"; Vm v; - v.exec("print(\"Hello world \"+random(-2.7,8.6));\ncreateObject(\"RaySheet\",[\"bstand_body1\"]);"); + v.setScene(&scene); + v.exec(code); // Set the engine's debugger console setDebugger(new Console()); @@ -113,21 +116,32 @@ Common::Error TwpEngine::run() { if (saveSlot != -1) (void)loadGameState(saveSlot); - _screen->copyRectToSurface(*surface, 1280 / 2, 720 / 2, rect); - - _screen->update(); - // Simple event handling loop Common::Event e; while (!shouldQuit()) { + const uint deltaTimeMs = 10; while (g_system->getEventManager()->pollEvent(e)) { } + // update threads + for (int i = 0; i < scene.threads.size(); i++) { + Thread *thread = scene.threads[i]; + if (thread->update(deltaTimeMs)) { + // TODO: delete it + } + } + + // update screen + _screen->clear(0xFF808080); + for (int i = 0; i < scene.entities.size(); i++) { + Entity &ett = scene.entities[i]; + _screen->transBlitFrom(ett.surface, ett.rect, Common::Point(ett.x, ett.y)); + } _screen->update(); // Delay for a bit. All events loops should have a delay // to prevent the system being unduly loaded - g_system->delayMillis(10); + g_system->delayMillis(deltaTimeMs); } return Common::kNoError; diff --git a/engines/twp/twp.h b/engines/twp/twp.h index df5e7730328..f54be7a1f5b 100644 --- a/engines/twp/twp.h +++ b/engines/twp/twp.h @@ -61,11 +61,9 @@ public: Common::String getGameId() const; /** - * Gets a random number + * Gets the random source */ - uint32 getRandomNumber(uint maxNum) { - return _randomSource.getRandomNumber(maxNum); - } + Common::RandomSource& getRandomSource() { return _randomSource; } bool hasFeature(EngineFeature f) const override { return diff --git a/engines/twp/vm.cpp b/engines/twp/vm.cpp index 55b0fb802da..dfa0a1a6763 100644 --- a/engines/twp/vm.cpp +++ b/engines/twp/vm.cpp @@ -19,11 +19,13 @@ * */ -#include -#include +#include "twp/twp.h" #include "twp/vm.h" +#include "common/array.h" +#include "common/algorithm.h" #include "common/debug.h" #include "common/random.h" +#include "image/png.h" #include "squirrel/squirrel.h" #include "squirrel/sqvm.h" #include "squirrel/sqobject.h" @@ -34,33 +36,84 @@ #include "squirrel/sqstdmath.h" #include "squirrel/sqstdio.h" #include "squirrel/sqstdaux.h" +#include "squirrel/sqfuncproto.h" +#include "squirrel/sqclosure.h" namespace Twp { -static SQRESULT get(HSQUIRRELVM v, int i, std::string &value) { +static Scene *gScene = nullptr; +static HSQUIRRELVM gVm = nullptr; + +static SQObjectPtr sqObj(HSQUIRRELVM v, const char *value) { + SQObjectPtr string = SQString::Create(_ss(v), value); + return string; +} + +static inline SQObject sqObj(HSQUIRRELVM v, int value) { + SQObject o; + o._type = OT_INTEGER; + o._unVal.nInteger = value; + return o; +} + +static SQRESULT get(HSQUIRRELVM v, int i, Common::String &value) { const SQChar *val; SQRESULT result = sq_getstring(v, i, &val); value = val; return result; } -static void getArray(HSQUIRRELVM v, HSQOBJECT o, std::vector &arr) { - sq_pushobject(v, o); - sq_pushnull(v); - while(SQ_SUCCEEDED(sq_next(v, -2))) { - const SQChar* str; - sq_getstring(v, -1, &str); - arr.push_back(str); - sq_pop(v, 2); - } - sq_pop(v, 1); +static void getArray(HSQUIRRELVM v, HSQOBJECT o, Common::Array &arr) { + sq_pushobject(v, o); + sq_pushnull(v); + while (SQ_SUCCEEDED(sq_next(v, -2))) { + const SQChar *str; + sq_getstring(v, -1, &str); + arr.push_back(str); + sq_pop(v, 2); + } + sq_pop(v, 1); } -static SQRESULT getArray(HSQUIRRELVM v, int i, std::vector &arr) { - HSQOBJECT obj; - SQRESULT result = sq_getstackobj(v, i, &obj); - getArray(v, obj, arr); - return result; +static SQRESULT getArray(HSQUIRRELVM v, int i, Common::Array &arr) { + HSQOBJECT obj; + SQRESULT result = sq_getstackobj(v, i, &obj); + getArray(v, obj, arr); + return result; +} + +static Thread *thread(HSQUIRRELVM v) { + return *Common::find_if(gScene->threads.begin(), gScene->threads.end(), [&](Thread *t) { + return t->threadObj._unVal.pThread == v; + }); +} + +template +static SQInteger breakfunc(HSQUIRRELVM v, const F &func) { + Thread *t = thread(v); + if (!t) + return sq_throwerror(v, "failed to get thread"); + t->suspend(); + func(*t); + return -666; +} + +// When called in a function started with startthread, execution is suspended for time seconds. +// It is an error to call breaktime in a function that was not started with startthread. +// +// . code-block:: Squirrel +// for (local x = 1; x < 4; x += 1) { +// playSound(soundPhoneRinging) +// breaktime(5.0) +// } +static SQInteger breaktime(HSQUIRRELVM v) { + SQFloat time; + if (SQ_FAILED(sq_getfloat(v, 2, &time))) + return sq_throwerror(v, "failed to get time"); + if (time == 0.f) + return breakfunc(v, [](Thread &t) { t.numFrames = 1; }); + else + return breakfunc(v, [&](Thread &t) { t.waitTime = time; }); } // Returns a random number from from to to inclusively. @@ -69,14 +122,13 @@ static SQRESULT getArray(HSQUIRRELVM v, int i, std::vector &arr) { // .. code-block:: Squirrel // wait_time = random(0.5, 2.0) static SQInteger sq_random(HSQUIRRELVM v) { - Common::RandomSource rnd("twp"); if (sq_gettype(v, 2) == OT_FLOAT || sq_gettype(v, 3) == OT_FLOAT) { SQFloat min, max; sq_getfloat(v, 2, &min); sq_getfloat(v, 3, &max); if (min > max) SWAP(min, max); - float scale = rnd.getRandomNumber(RAND_MAX) / (float)RAND_MAX; + float scale = g_engine->getRandomSource().getRandomNumber(RAND_MAX) / (float)RAND_MAX; SQFloat value = min + scale * (max - min); sq_pushfloat(v, value); } else { @@ -85,7 +137,7 @@ static SQInteger sq_random(HSQUIRRELVM v) { sq_getinteger(v, 3, &max); if (min > max) SWAP(min, max); - SQInteger value = rnd.getRandomNumberRngSigned(min, max); + SQInteger value = g_engine->getRandomSource().getRandomNumberRngSigned(min, max); sq_pushinteger(v, value); } return 1; @@ -99,8 +151,8 @@ static SQInteger sq_random(HSQUIRRELVM v) { // They do not have verbs or local variables by default, but these can be added when the object is created so it can be used in the construction of sentences. static SQInteger createObject(HSQUIRRELVM v) { SQInteger numArgs = sq_gettop(v); - std::string sheet; - std::vector frames; + Common::String sheet; + Common::Array frames; SQInteger framesIndex = 2; // get sheet parameter if any @@ -114,7 +166,7 @@ static SQInteger createObject(HSQUIRRELVM v) { if (numArgs >= 2) { switch (sq_gettype(v, framesIndex)) { case OT_STRING: { - std::string frame; + Common::String frame; get(v, framesIndex, frame); frames.push_back(frame); } break; @@ -126,12 +178,156 @@ static SQInteger createObject(HSQUIRRELVM v) { } } - debug("Create object: %s, %zu", sheet.c_str(), frames.size()); - // var obj = gEngine.room.createObject(sheet, frames) - // push(v, obj.table) + debug("Create object: %s, %u", sheet.c_str(), frames.size()); + + // TODO: share resources load png to surface + GGPackEntryReader r; + r.open(gScene->pack, sheet + ".png"); + Image::PNGDecoder d; + d.loadStream(r); + const Graphics::Surface *surface = d.getSurface(); + + // load sheet json + GGPackEntryReader r2; + r2.open(gScene->pack, sheet + ".json"); + Common::Array data(r2.size()); + r2.read(&data[0], r2.size()); + Common::String s(&data[0], r2.size()); + + debug(s.c_str()); + + // TODO: change to load each frame + Common::JSONValue *json = Common::JSON::parse(s.c_str()); + const Common::JSONObject &jRect = json->asObject()["frames"]->asObject()[frames[0]]->asObject()["frame"]->asObject(); + + int x = (int)jRect["x"]->asIntegerNumber(); + int y = (int)jRect["y"]->asIntegerNumber(); + int w = (int)jRect["w"]->asIntegerNumber(); + int h = (int)jRect["h"]->asIntegerNumber(); + + delete json; + + // TODO: create an entity + Entity e; + e.surface.copyFrom(*surface); + e.rect = Common::Rect(x, y, x + w, y + h); + + static int gId = 3000; + sq_newtable(v); + sq_pushstring(v, _SC("_id"), -1); + sq_pushinteger(v, gId++); + sq_newslot(v, -3, SQFalse); + sq_getstackobj(v, -1, &e.obj); + sq_addref(gVm, &e.obj); + sq_pop(v, 1); + + gScene->entities.push_back(e); + + sq_pushobject(v, e.obj); + return 1; } +static SQInteger _startthread(HSQUIRRELVM v, bool global) { + SQInteger size = sq_gettop(v); + + Thread *t = new Thread(); + t->global = global; + + static uint64 gThreadId = 300000; + sq_newtable(v); + sq_pushstring(v, _SC("_id"), -1); + sq_pushinteger(v, gThreadId++); + sq_newslot(v, -3, SQFalse); + sq_getstackobj(v, -1, &t->obj); + sq_addref(gVm, &t->obj); + sq_pop(v, 1); + + sq_resetobject(&t->envObj); + if (SQ_FAILED(sq_getstackobj(v, 1, &t->envObj))) + return sq_throwerror(v, "Couldn't get environment from stack"); + sq_addref(gVm, &t->envObj); + + // create thread and store it on the stack + sq_newthread(gVm, 1024); + sq_resetobject(&t->threadObj); + if (SQ_FAILED(sq_getstackobj(gVm, -1, &t->threadObj))) + return sq_throwerror(v, "Couldn't get coroutine thread from stack"); + sq_addref(gVm, &t->threadObj); + + for (int i = 0; i < size - 2; i++) { + HSQOBJECT arg; + sq_resetobject(&arg); + if (SQ_FAILED(sq_getstackobj(v, 3 + i, &arg))) + return sq_throwerror(v, "Couldn't get coroutine args from stack"); + t->args.push_back(arg); + sq_addref(gVm, &arg); + } + + // get the closure + sq_resetobject(&t->closureObj); + if (SQ_FAILED(sq_getstackobj(v, 2, &t->closureObj))) + return sq_throwerror(v, "Couldn't get coroutine thread from stack"); + sq_addref(gVm, &t->closureObj); + + const SQChar *name = nullptr; + if (SQ_SUCCEEDED(sq_getclosurename(v, 2))) + sq_getstring(v, -1, &name); + + t->name = Common::String::format("%s %s (%lld)", name == nullptr ? "" : name, _stringval(_closure(t->closureObj)->_function->_sourcename), _closure(t->closureObj)->_function->_lineinfos->_line); + sq_pop(gVm, 1); + if (name) + sq_pop(v, 1); // pop name + sq_pop(v, 1); // pop closure + + gScene->threads.push_back(t); + + debug("create thread %s", t->name.c_str()); + + // call the closure in the thread + if (!t->call()) + return sq_throwerror(v, "call failed"); + + sq_pushobject(v, t->obj); + return 1; +} + +static SQInteger startthread(HSQUIRRELVM v) { + return _startthread(v, false); +} + +static SQInteger startglobalthread(HSQUIRRELVM v) { + return _startthread(v, true); +} + +static SQInteger objectAt(HSQUIRRELVM v) { + HSQOBJECT o; + sq_getstackobj(v, 2, &o); + + SQObjectPtr id; + _table(o)->Get(sqObj(v, "_id"), id); + + Entity *ett = Common::find_if(gScene->entities.begin(), gScene->entities.end(), [&](Entity &e) { + SQObjectPtr id2; + _table(e.obj)->Get(sqObj(v, "_id"), id2); + return _integer(id) == _integer(id2); + }); + + if (!ett) + return sq_throwerror(v, "failed to get object"); + + SQInteger x, y; + if (SQ_FAILED(sq_getinteger(v, 3, &x))) + return sq_throwerror(v, "failed to get x"); + if (SQ_FAILED(sq_getinteger(v, 4, &y))) + return sq_throwerror(v, "failed to get y"); + ett->x = x; + ett->y = y; + debug("Object at: %lld, %lld", x, y); + + return 0; +} + static void regFunc(HSQUIRRELVM v, SQFUNCTION f, const SQChar *functionName, SQInteger nparamscheck, const SQChar *typemask) { sq_pushroottable(v); sq_pushstring(v, functionName, -1); @@ -142,18 +338,6 @@ static void regFunc(HSQUIRRELVM v, SQFUNCTION f, const SQChar *functionName, SQI sq_pop(v, 1); // pops the root table } -static SQObjectPtr sqObj(HSQUIRRELVM v, const char *value) { - SQObjectPtr string = SQString::Create(_ss(v), value); - return string; -} - -static inline SQObject sqObj(HSQUIRRELVM v, int value) { - SQObject o; - o._type = OT_INTEGER; - o._unVal.nInteger = value; - return o; -} - static void sqExec(HSQUIRRELVM v, const char *code) { SQInteger top = sq_gettop(v); if (SQ_FAILED(sq_compilebuffer(v, code, strlen(code), "twp", SQTrue))) { @@ -202,23 +386,37 @@ static void printfunc(HSQUIRRELVM v, const SQChar *s, ...) { debug("TWP: %s", buf); } -Vm::Vm() { - v = sq_open(1024); +Vm::Vm() + : _scene(nullptr) { + gVm = v = sq_open(1024); sq_setcompilererrorhandler(v, errorHandler); sq_newclosure(v, aux_printerror, 0); sq_seterrorhandler(v); sq_setprintfunc(v, printfunc, printfunc); + + sq_pushroottable(v); sqstd_register_stringlib(v); sqstd_register_mathlib(v); sqstd_register_iolib(v); + sq_pop(v, 1); + regFunc(v, sq_random, _SC("random"), 0, nullptr); regFunc(v, createObject, _SC("createObject"), 0, nullptr); + regFunc(v, startthread, _SC("startthread"), 0, nullptr); + regFunc(v, startglobalthread, _SC("startglobalthread"), 0, nullptr); + regFunc(v, objectAt, _SC("objectAt"), 0, nullptr); + regFunc(v, breaktime, _SC("breaktime"), 0, nullptr); SQObject platform = sqObj(v, 666); _table(v->_roottable)->NewSlot(sqObj(v, _SC("PLATFORM")), SQObjectPtr(platform)); } Vm::~Vm() { + if(_scene) { + for (int i = 0; i < _scene->threads.size(); i++) { + delete _scene->threads[i]; + } + } sq_close(v); } @@ -226,4 +424,75 @@ void Vm::exec(const SQChar *code) { sqExec(v, code); } +void Vm::setScene(Scene *scene) { + gScene = _scene = scene; +} + +Thread::Thread() : paused(false), waitTime(0), numFrames(0), stopRequest(false) { +} + +Thread::~Thread() { + for (int i = 0; i < args.size(); i++) { + sq_release(gVm, &args[i]); + } + sq_release(gVm, &threadObj); + sq_release(gVm, &envObj); + sq_release(gVm, &closureObj); +} + +bool Thread::call() { + HSQUIRRELVM v = threadObj._unVal.pThread; + // call the closure in the thread + SQInteger top = sq_gettop(v); + sq_pushobject(v, closureObj); + sq_pushobject(v, envObj); + for (int i = 0; i < args.size(); i++) { + sq_pushobject(v, args[i]); + } + if (SQ_FAILED(sq_call(v, 1 + args.size(), SQFalse, SQTrue))) { + sq_settop(v, top); + return false; + } + return true; +} + +bool Thread::isDead() { + SQInteger state = sq_getvmstate(threadObj._unVal.pThread); + return stopRequest || state == 0; +} + +bool Thread::isSuspended() { + SQInteger state = sq_getvmstate(threadObj._unVal.pThread); + return state != 1; +} + +void Thread::suspend() { + // TODO: pauseable + if (!isSuspended()) { + sq_suspendvm(threadObj._unVal.pThread); + } +} + +void Thread::resume() { + if (!isDead() && isSuspended()) { + sq_wakeupvm(threadObj._unVal.pThread, SQFalse, SQFalse, SQTrue, SQFalse); + } +} + +bool Thread::update(float elapsed) { + if (paused) { + } else if (waitTime > 0) { + waitTime -= elapsed; + if (waitTime <= 0) { + waitTime = 0; + resume(); + } + } else if (numFrames > 0) { + numFrames -= 1; + numFrames = 0; + resume(); + } + return isDead(); +} + } // namespace Twp diff --git a/engines/twp/vm.h b/engines/twp/vm.h index 884ae65d530..f42951e1a41 100644 --- a/engines/twp/vm.h +++ b/engines/twp/vm.h @@ -22,17 +22,64 @@ #ifndef TWPVM_H #define TWPVM_H -#include "squirrel/squirrel.h" +#include "common/array.h" +#include "common/rect.h" +#include "graphics/surface.h" +#include "twp/squirrel/squirrel.h" +#include "twp/ggpack.h" namespace Twp { +class Entity { +public: + HSQOBJECT obj; + Graphics::ManagedSurface surface; + Common::Rect rect; + int x; + int y; +}; + +class Thread { +public: + Thread(); + ~Thread(); + + bool call(); + bool update(float elapsed); + void suspend(); + void resume(); + bool isDead(); + bool isSuspended(); + +public: + uint64 id; + Common::String name; + bool global; + HSQOBJECT obj, threadObj, envObj, closureObj; + Common::Array args; + bool paused; + float waitTime; + int numFrames; + bool stopRequest; +}; + +class Scene { +public: + Common::Array entities; + Common::Array threads; + GGPackDecoder pack; +}; + class Vm { public: Vm(); ~Vm(); + + void setScene(Scene* scene); void exec(const SQChar *code); private: HSQUIRRELVM v; + Scene* _scene; }; } // End of namespace Twp