| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384 |
- #include "EngineObject.h"
- #include "ScriptEngine.h"
- #include "WallpaperEngine/Logging/Log.h"
- #include "WallpaperEngine/Audio/Drivers/AudioDriver.h"
- #include "WallpaperEngine/Audio/Drivers/Recorders/PlaybackRecorder.h"
- #include "WallpaperEngine/Render/Wallpapers/CScene.h"
- #include <ranges>
- using namespace WallpaperEngine::Scripting;
- extern float g_Time;
- extern float g_TimeLast;
- extern float g_Daytime;
- static uint32_t EngineInstanceId = 0;
- std::map<uint32_t, EngineObject&> engineInstances;
- JSValue engine_set_value (JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { return JS_EXCEPTION; }
- JSValue engine_open_user_shortcut (JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) {
- return JS_UNDEFINED;
- }
- JSValue engine_get_frametime (JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) {
- return JS_NewFloat64 (ctx, g_Time - g_TimeLast);
- }
- JSValue engine_get_runtime (JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) {
- return JS_NewFloat64 (ctx, g_Time);
- }
- JSValue engine_get_daytime (JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) {
- return JS_NewFloat64 (ctx, g_Daytime);
- }
- JSValue engine_stop_interval (
- JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValueConst* func_data
- ) {
- if (argc != 1) {
- return JS_EXCEPTION;
- }
- const auto it = engineInstances.find (magic);
- if (it == engineInstances.end ()) {
- return JS_EXCEPTION;
- }
- int id = 0;
- JS_ToInt32 (ctx, &id, argv[0]);
- it->second.clearInterval (id);
- return JS_UNDEFINED;
- }
- JSValue engine_stop_timeout (
- JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValueConst* func_data
- ) {
- if (argc != 1) {
- return JS_EXCEPTION;
- }
- const auto it = engineInstances.find (magic);
- if (it == engineInstances.end ()) {
- return JS_EXCEPTION;
- }
- int id = 0;
- JS_ToInt32 (ctx, &id, argv[0]);
- it->second.clearTimeout (id);
- return JS_UNDEFINED;
- }
- JSValue engine_set_interval (JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic) {
- if (argc < 1) {
- return JS_EXCEPTION;
- }
- int delay = 0;
- if (argc > 1) {
- JS_ToInt32 (ctx, &delay, argv[1]);
- }
- JSValue function = argv[0];
- if (!JS_IsFunction (ctx, function)) {
- return JS_EXCEPTION;
- }
- const auto it = engineInstances.find (magic);
- if (it == engineInstances.end ()) {
- return JS_EXCEPTION;
- }
- int id = it->second.reserveNextIntervalId (function, delay);
- JSValue args[] = { JS_NewInt32 (ctx, id) };
- return JS_NewCFunctionData (ctx, engine_stop_interval, 2, magic, 1, args);
- }
- // Backs the "average"/"left"/"right" getters on the object returned by registerAudioBuffers().
- // The recorder only ever produces one (mono) spectrum - see PulseAudioPlaybackRecorder - so all
- // three read the same data, matching how CPass already binds it to both the Left and Right
- // g_AudioSpectrum shader uniforms.
- JSValue audio_buffer_get_values (
- JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValueConst* func_data
- ) {
- int engineInstanceId = 0;
- int resolution = 32;
- JS_ToInt32 (ctx, &engineInstanceId, func_data[0]);
- JS_ToInt32 (ctx, &resolution, func_data[1]);
- JSValue result = JS_NewArray (ctx);
- const auto it = engineInstances.find (engineInstanceId);
- if (it == engineInstances.end ()) {
- sLog.error ("registerAudioBuffers: no EngineObject found for instance ", engineInstanceId, " - returning zeros");
- return result;
- }
- const auto& recorder = it->second.getScene ().getAudioContext ().getDriver ().getRecorder ();
- const float* data = recorder.audio32;
- if (resolution == 16) {
- data = recorder.audio16;
- } else if (resolution == 64) {
- data = recorder.audio64;
- }
- static int diagnosticCounter = 0;
- if (++diagnosticCounter >= 500) {
- diagnosticCounter = 0;
- sLog.debug ("registerAudioBuffers: average[0..3] = ", data[0], ", ", data[1], ", ", data[2], ", ", data[3]);
- }
- for (int i = 0; i < resolution; i++) {
- JS_SetPropertyUint32 (ctx, result, i, JS_NewFloat64 (ctx, data[i]));
- }
- return result;
- }
- // engine.registerAudioBuffers(resolution): resolution must be 16, 32 or 64 (falls back to 32
- // otherwise), matching the AUDIO_RESOLUTION_* constants below. Returns an object whose
- // average/left/right properties are re-read from the live FFT spectrum every access, so scripts
- // that poll them from an update() callback see current values each frame.
- JSValue engine_register_audio_buffers (JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic) {
- int resolution = 32;
- if (argc > 0) {
- JS_ToInt32 (ctx, &resolution, argv[0]);
- }
- if (resolution != 16 && resolution != 32 && resolution != 64) {
- resolution = 32;
- }
- JSValue result = JS_NewObject (ctx);
- static constexpr const char* properties[] = { "average", "left", "right" };
- for (const char* property : properties) {
- JSValue closureData[] = { JS_NewInt32 (ctx, magic), JS_NewInt32 (ctx, resolution) };
- JS_DefinePropertyGetSet (
- ctx, result, JS_NewAtom (ctx, property),
- JS_NewCFunctionData (ctx, audio_buffer_get_values, 0, 0, 2, closureData),
- JS_NewCFunction (ctx, engine_set_value, "set", 1), JS_PROP_ENUMERABLE
- );
- }
- return result;
- }
- JSValue engine_set_timeout (JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic) {
- if (argc < 1) {
- return JS_EXCEPTION;
- }
- int delay = 0;
- if (argc > 1) {
- JS_ToInt32 (ctx, &delay, argv[1]);
- }
- JSValue function = argv[0];
- if (!JS_IsFunction (ctx, function)) {
- return JS_EXCEPTION;
- }
- const auto it = engineInstances.find (magic);
- if (it == engineInstances.end ()) {
- return JS_EXCEPTION;
- }
- int id = it->second.reserveNextTimeoutId (function, delay);
- JSValue args[] = { JS_NewInt32 (ctx, id) };
- return JS_NewCFunctionData (ctx, engine_stop_timeout, 2, magic, 1, args);
- }
- EngineObject::EngineObject (ScriptEngine& engine, Render::Wallpapers::CScene& scene) :
- m_scene (scene), m_engine (engine), m_instanceId (++EngineInstanceId), m_classId (0) {
- // required so setInterval/setTimeout/registerAudioBuffers's magic-encoded instance id can find
- // their way back to this object from the free-standing JS callback functions above
- engineInstances.emplace (this->m_instanceId, *this);
- this->m_definition = { .class_name = "IEngine" };
- JS_NewClassID (this->m_engine.getRuntime (), &this->m_classId);
- JS_NewClass (this->m_engine.getRuntime (), this->m_classId, &this->m_definition);
- this->m_instance = JS_NewObjectClass (this->m_engine.getContext (), this->m_classId);
- JS_DupValue (this->m_engine.getContext (), this->m_instance);
- // set properties
- JS_SetOpaque (this->m_instance, this);
- JS_DefinePropertyGetSet (
- this->m_engine.getContext (), this->m_instance, JS_NewAtom (this->m_engine.getContext (), "frametime"),
- JS_NewCFunction (this->m_engine.getContext (), engine_get_frametime, "get", 0),
- JS_NewCFunction (this->m_engine.getContext (), engine_set_value, "set", 1), JS_PROP_ENUMERABLE
- );
- JS_DefinePropertyGetSet (
- this->m_engine.getContext (), this->m_instance, JS_NewAtom (this->m_engine.getContext (), "runtime"),
- JS_NewCFunction (this->m_engine.getContext (), engine_get_runtime, "get", 0),
- JS_NewCFunction (this->m_engine.getContext (), engine_set_value, "set", 1), JS_PROP_ENUMERABLE
- );
- JS_DefinePropertyGetSet (
- this->m_engine.getContext (), this->m_instance, JS_NewAtom (this->m_engine.getContext (), "timeOfDay"),
- JS_NewCFunction (this->m_engine.getContext (), engine_get_daytime, "get", 0),
- JS_NewCFunction (this->m_engine.getContext (), engine_set_value, "set", 1), JS_PROP_ENUMERABLE
- );
- JS_DefinePropertyValueStr (
- this->m_engine.getContext (), this->m_instance, "AUDIO_RESOLUTION_16",
- JS_NewInt32 (this->m_engine.getContext (), 16), JS_PROP_ENUMERABLE
- );
- JS_DefinePropertyValueStr (
- this->m_engine.getContext (), this->m_instance, "AUDIO_RESOLUTION_32",
- JS_NewInt32 (this->m_engine.getContext (), 32), JS_PROP_ENUMERABLE
- );
- JS_DefinePropertyValueStr (
- this->m_engine.getContext (), this->m_instance, "AUDIO_RESOLUTION_64",
- JS_NewInt32 (this->m_engine.getContext (), 64), JS_PROP_ENUMERABLE
- );
- JS_DefinePropertyValueStr (
- this->m_engine.getContext (), this->m_instance, "setInterval",
- JS_NewCFunctionMagic (
- this->m_engine.getContext (), engine_set_interval, "setInterval", 2, JS_CFUNC_generic_magic,
- this->m_instanceId
- ),
- JS_PROP_ENUMERABLE
- );
- JS_DefinePropertyValueStr (
- this->m_engine.getContext (), this->m_instance, "setTimeout",
- JS_NewCFunctionMagic (
- this->m_engine.getContext (), engine_set_timeout, "setTimeout", 2, JS_CFUNC_generic_magic,
- this->m_instanceId
- ),
- JS_PROP_ENUMERABLE
- );
- JS_DefinePropertyValueStr (
- this->m_engine.getContext (), this->m_instance, "openUserShortcut",
- JS_NewCFunction (this->m_engine.getContext (), engine_open_user_shortcut, "openUserShortcut", 0),
- JS_PROP_ENUMERABLE
- );
- JS_DefinePropertyValueStr (
- this->m_engine.getContext (), this->m_instance, "registerAudioBuffers",
- JS_NewCFunctionMagic (
- this->m_engine.getContext (), engine_register_audio_buffers, "registerAudioBuffers", 1,
- JS_CFUNC_generic_magic, this->m_instanceId
- ),
- JS_PROP_ENUMERABLE
- );
- // TODO: ADD THE REST OF THE DEFINITION!
- }
- EngineObject::~EngineObject () {
- // clear all the timeouts and intervals
- for (const auto& [id, timeout] : this->m_timeouts) {
- JS_FreeValue (this->m_engine.getContext (), timeout.callback);
- }
- for (const auto& [id, interval] : this->m_intervals) {
- JS_FreeValue (this->m_engine.getContext (), interval.callback);
- }
- engineInstances.erase (this->m_instanceId);
- this->m_intervals.clear ();
- this->m_timeouts.clear ();
- JS_FreeValue (this->m_engine.getContext (), this->m_instance);
- }
- uint32_t EngineObject::reserveNextTimeoutId (JSValue function, uint64_t duration) {
- const auto id = ++this->m_nextTimeoutId;
- this->m_timeouts[id] = Timeout { .callback = function,
- .duration = std::chrono::milliseconds (duration),
- .next = std::chrono::steady_clock::now () + std::chrono::milliseconds (duration) };
- return id;
- }
- uint32_t EngineObject::reserveNextIntervalId (JSValue function, uint64_t duration) {
- const auto id = ++this->m_nextIntervalId;
- this->m_intervals[id]
- = Timeout { .callback = function,
- .duration = std::chrono::milliseconds (duration),
- .next = std::chrono::steady_clock::now () + std::chrono::milliseconds (duration) };
- return id;
- }
- void EngineObject::clearInterval (uint32_t id) {
- const auto it = this->m_intervals.find (id);
- if (it == this->m_intervals.end ()) {
- return;
- }
- JS_FreeValue (this->getEngine ().getContext (), it->second.callback);
- this->m_intervals.erase (id);
- }
- void EngineObject::clearTimeout (uint32_t id) {
- const auto it = this->m_timeouts.find (id);
- if (it == this->m_timeouts.end ()) {
- return;
- }
- JS_FreeValue (this->getEngine ().getContext (), it->second.callback);
- this->m_timeouts.erase (id);
- }
- void EngineObject::tick () {
- const auto now = std::chrono::steady_clock::now ();
- // check any interval and run them if needed
- for (auto& timeout : this->m_intervals | std::views::values) {
- if (timeout.next > now) {
- continue;
- }
- timeout.next = now + timeout.duration;
- JS_Call (this->m_engine.getContext (), timeout.callback, JS_NULL, 0, nullptr);
- }
- std::vector<uint32_t> removeTimeouts;
- // check any timeout and run them if needed
- for (auto& [id, timeout] : this->m_timeouts) {
- if (timeout.next > now) {
- continue;
- }
- JS_Call (this->m_engine.getContext (), timeout.callback, JS_NULL, 0, nullptr);
- JS_FreeValue (this->m_engine.getContext (), timeout.callback);
- removeTimeouts.push_back (id);
- }
- for (auto id : removeTimeouts) {
- this->m_timeouts.erase (id);
- }
- }
|