TWP: Test threads and breaktime

This commit is contained in:
scemino authored and Eugene Sandulenko committed 2024-03-07 20:08:26 +01:00
1 parent 00274a3565
commit 4e27881da2
4 files changed
+403 -75

No files matched your search

+43 -29
View File
@@ -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<char> 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;
+2 -4
View File
@@ -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
+310 -41
View File
@@ -19,11 +19,13 @@
*
*/
#include <vector>
#include <string>
#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<std::string> &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<Common::String> &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<std::string> &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<Common::String> &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<typename F>
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<std::string> &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<std::string> frames;
Common::String sheet;
Common::Array<Common::String> 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<char> 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 ? "<anonymous>" : 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
+48 -1
View File
@@ -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<HSQOBJECT> args;
bool paused;
float waitTime;
int numFrames;
bool stopRequest;
};
class Scene {
public:
Common::Array<Entity> entities;
Common::Array<Thread*> 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