ScriptEngine.h 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. #pragma once
  2. #include "Adapters/VectorAdapter.h"
  3. #include "ConsoleObject.h"
  4. #include "EngineObject.h"
  5. #include "InputObject.h"
  6. #include "Modules/ScriptModule.h"
  7. #include "SceneObject.h"
  8. #include <chrono>
  9. #include <future>
  10. #include <map>
  11. #include <memory>
  12. #include <optional>
  13. #include <string>
  14. #include <unordered_map>
  15. #include <unordered_set>
  16. #include "WallpaperEngine/Data/Model/DynamicValue.h"
  17. #include "WallpaperEngine/Data/Model/Types.h"
  18. extern "C" {
  19. #include "quickjs.h"
  20. }
  21. namespace WallpaperEngine::Render::Wallpapers {
  22. class CScene;
  23. }
  24. namespace WallpaperEngine::Scripting {
  25. class ScriptPropertiesObject;
  26. namespace Adapters {
  27. class ScriptableObjectAdapter;
  28. }
  29. using namespace WallpaperEngine::Data::Model;
  30. // Opaque handle returned by createLayerScript. 0 means invalid / not created.
  31. using ScriptLayerHandle = int;
  32. static constexpr ScriptLayerHandle kInvalidLayerHandle = 0;
  33. class ScriptEngine {
  34. public:
  35. struct LoadedModule {
  36. DynamicValue& value;
  37. JSValue module;
  38. };
  39. struct JSObjectAdapters {
  40. std::unique_ptr<Adapters::VectorAdapter<4>> vec4;
  41. std::unique_ptr<Adapters::VectorAdapter<3>> vec3;
  42. std::unique_ptr<Adapters::VectorAdapter<2>> vec2;
  43. std::unique_ptr<Adapters::ScriptableObjectAdapter> object;
  44. };
  45. ~ScriptEngine ();
  46. ScriptEngine (Render::Wallpapers::CScene& scene);
  47. ScriptEngine (const ScriptEngine&) = delete;
  48. ScriptEngine& operator= (const ScriptEngine&) = delete;
  49. JSRuntime* getRuntime () const { return m_runtime; }
  50. JSContext* getContext () const { return m_context; }
  51. JSValue getGlobalThis () const { return m_globalThis; }
  52. LoadedModule* getRunningModule () const { return m_runningModule; }
  53. JSValue dynamicToJs (DynamicValue& value) const;
  54. /**
  55. * Evaluate a WallpaperEngine script's update() function.
  56. *
  57. * @param key The full JS script text (ES6 module with export function update(value))
  58. * @param currentValue The current value to pass to update()
  59. * @return The modified value from update(), or a copy of currentValue on error
  60. */
  61. void queueScript (const std::string& key, DynamicValue& currentValue, ScriptableObject& object);
  62. /**
  63. * Runs a frame tick in the javascript engine. Dispatches any pending events,
  64. * timeouts, intervals AND calls any update() functions.
  65. */
  66. void tick ();
  67. // -------------------------------------------------------------------
  68. // Layer-script API (Phase 2 — dynamic text)
  69. // -------------------------------------------------------------------
  70. //
  71. // Wallpaper Engine text-object scripts follow a lifecycle pattern that
  72. // cannot be evaluated with the simple `update(value) -> value` contract
  73. // above. They typically look like:
  74. //
  75. // 'use strict';
  76. // export var scriptProperties = createScriptProperties()…finish();
  77. // export function init() { /* subscribe to events, cache data */ }
  78. // export function update() { thisLayer.text = computeCurrentText(); }
  79. //
  80. // The script mutates a `thisLayer` object in place instead of returning
  81. // a value, and lifecycle functions are optional. The API below keeps
  82. // per-layer state alive across frames so `init()` runs once and
  83. // `update()` re-runs every tick.
  84. /**
  85. * Create a persistent "layer script" from a WE text-object script.
  86. *
  87. * @param scriptSource The full JS script text.
  88. * @param initialScriptProps Initial values for scriptProperties entries.
  89. * Ownership stays with the caller; we only snapshot current values.
  90. * @param initialText Initial value of `thisLayer.text` (usually the
  91. * static placeholder carried in the JSON).
  92. * @return A positive handle, or kInvalidLayerHandle if evaluation failed.
  93. */
  94. ScriptLayerHandle createLayerScript (
  95. const std::string& scriptSource, std::map<std::string, UserSettingUniquePtr>& initialScriptProps,
  96. const std::string& initialText
  97. );
  98. /**
  99. * Advance a layer by one frame.
  100. *
  101. * On the first call, invokes `init()` (if defined) before `update()`.
  102. * Updates a `thisScene` context visible to the script (time, fps).
  103. * Silently no-ops if the handle is invalid.
  104. */
  105. void tickLayer (ScriptLayerHandle handle, double time, double deltaTime, double fps);
  106. /**
  107. * Read the current value of `thisLayer.text` for the given layer.
  108. * Returns an empty string if the handle is invalid.
  109. */
  110. std::string layerText (ScriptLayerHandle handle);
  111. /**
  112. * Tear down a layer: invokes `destroy()` (if defined) and frees state.
  113. */
  114. void destroyLayer (ScriptLayerHandle handle);
  115. const JSObjectAdapters& getAdapters () const { return m_adapters; }
  116. const Render::Wallpapers::CScene& getScene () const { return m_scene; }
  117. const std::map<std::string, std::unique_ptr<Modules::ScriptModule>>& getModules () const { return m_modules; }
  118. private:
  119. JSValue call (JSValue module, int argc, JSValueConst argv[], const char* name);
  120. void installBuiltins ();
  121. void refreshMediaState ();
  122. void dispatchMediaEvents (JSValue module, const void* bindingKey);
  123. void callModuleWithProps (JSContext* ctx, JSValue module, const char* name, JSValue propsObj) const;
  124. void runIntervals (JSContext* ctx, JSValue globalObj, const std::string& bindingKeyString) const;
  125. DynamicValueUniquePtr fallbackTextValue (const std::string& scriptSource) const;
  126. void applyTextFallback (DynamicValue& value, const std::string& scriptSource) const;
  127. struct MediaState {
  128. int playbackState = 0;
  129. std::string status = "Stopped";
  130. std::string title;
  131. std::string artist;
  132. std::string artUrl;
  133. double duration = 0.0;
  134. double position = 0.0;
  135. bool available = false;
  136. };
  137. // Installs globalThis.__layers and related helpers. Called lazily.
  138. void ensureLayerRegistry ();
  139. JSRuntime* m_runtime = nullptr;
  140. JSContext* m_context = nullptr;
  141. JSValue m_globalThis;
  142. Render::Wallpapers::CScene& m_scene;
  143. std::unique_ptr<EngineObject> m_engineObject;
  144. std::unique_ptr<InputObject> m_inputObject;
  145. std::unique_ptr<SceneObject> m_sceneObject;
  146. std::unique_ptr<ConsoleObject> m_consoleObject;
  147. std::unique_ptr<ScriptPropertiesObject> m_scriptPropertiesObject;
  148. std::map<std::string, std::unique_ptr<Modules::ScriptModule>> m_modules = {};
  149. std::map<std::string, LoadedModule> m_scriptModules = {};
  150. LoadedModule* m_runningModule = nullptr;
  151. ScriptLayerHandle m_nextLayerId = 1;
  152. bool m_layerRegistryReady = false;
  153. std::map<ScriptLayerHandle, bool> m_layerInitialized;
  154. std::unordered_set<const void*> m_initializedModules = {};
  155. std::unordered_map<const void*, std::string> m_lastMediaProperties = {};
  156. std::unordered_map<const void*, std::string> m_lastMediaPlayback = {};
  157. std::unordered_map<const void*, std::string> m_lastMediaTimeline = {};
  158. std::unordered_map<const void*, std::string> m_lastMediaThumbnail = {};
  159. std::chrono::steady_clock::time_point m_lastMediaPoll = {};
  160. std::future<std::optional<MediaState>> m_mediaPollFuture = {};
  161. MediaState m_mediaState = {};
  162. bool m_builtinsInstalled = false;
  163. JSObjectAdapters m_adapters;
  164. };
  165. } // namespace WallpaperEngine::Scripting