@geastack/core 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +36 -0
  3. package/app_frame_perf.cpp +77 -0
  4. package/css.d.ts +1 -0
  5. package/events.cpp +67 -0
  6. package/gea_app_entry.cpp +317 -0
  7. package/gea_sources.mjs +126 -0
  8. package/gea_sources.sh +36 -0
  9. package/include/app.h +60 -0
  10. package/include/apps.h +46 -0
  11. package/include/audio.h +90 -0
  12. package/include/ble.h +212 -0
  13. package/include/bluetooth/hid_profile.h +92 -0
  14. package/include/camera.h +107 -0
  15. package/include/canvas.h +290 -0
  16. package/include/display.h +390 -0
  17. package/include/display_present.h +869 -0
  18. package/include/event.h +32 -0
  19. package/include/events.h +116 -0
  20. package/include/gea/embedded.h +13 -0
  21. package/include/gea/reactive_runtime.h +32 -0
  22. package/include/gea.h +14 -0
  23. package/include/gea_perf_config.h +96 -0
  24. package/include/geaos/abi.h +165 -0
  25. package/include/geolocation.h +126 -0
  26. package/include/gpio.h +61 -0
  27. package/include/graphics/font.h +122 -0
  28. package/include/i2c.h +20 -0
  29. package/include/image.h +156 -0
  30. package/include/imu.h +23 -0
  31. package/include/input.h +33 -0
  32. package/include/memory.h +52 -0
  33. package/include/pixel.h +907 -0
  34. package/include/platform/file_cache.h +18 -0
  35. package/include/power.h +11 -0
  36. package/include/runtime.h +83 -0
  37. package/include/services/app_runner.h +10 -0
  38. package/include/services/app_state.h +14 -0
  39. package/include/services/battery_service.h +12 -0
  40. package/include/services/bluetooth_service.h +13 -0
  41. package/include/services/diagnostics.h +76 -0
  42. package/include/services/diagnostics_platform.h +54 -0
  43. package/include/services/frame_scheduler.h +71 -0
  44. package/include/services/mirror.h +49 -0
  45. package/include/services/network_services.h +25 -0
  46. package/include/services/ota.h +32 -0
  47. package/include/services/runtime_log.h +11 -0
  48. package/include/services/storage_service.h +26 -0
  49. package/include/touch.h +38 -0
  50. package/include/ui/signal.h +149 -0
  51. package/include/wifi.h +67 -0
  52. package/index.d.ts +1940 -0
  53. package/jsx.d.ts +1 -0
  54. package/package.json +70 -0
  55. package/runtime/ble.ts +49 -0
  56. package/runtime/color.ts +21 -0
  57. package/runtime/compiler.ts +43 -0
  58. package/runtime/host.ts +1083 -0
  59. package/runtime/images.ts +129 -0
  60. package/runtime/primitives.ts +49 -0
  61. package/runtime-surface.ts +180 -0
  62. package/runtime.cpp +431 -0
  63. package/runtime.ts +34 -0
  64. package/scripts/build-gea-vite-geatsc.mjs +4514 -0
  65. package/scripts/dotenv-defines.mjs +98 -0
  66. package/scripts/gea-bundle-type-hints.mjs +546 -0
  67. package/scripts/gea-embedded-compat-staging.mjs +17 -0
  68. package/scripts/gea-embedded-compat-transform.mjs +427 -0
  69. package/scripts/gea-vite-module-graph-plugin.mjs +578 -0
  70. package/scripts/generate-gea-embedded-assets.mjs +296 -0
  71. package/scripts/generate-gea-embedded-fonts.mjs +1252 -0
  72. package/scripts/generate-installed-app-plan.mjs +55 -0
  73. package/scripts/native-object-cache.sh +244 -0
@@ -0,0 +1,317 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Adapter that runs a gea app compiled as:
3
+ // TSX -> vite-plugin-gea JS -> geatsc C++.
4
+
5
+ #include "app.h"
6
+ #include "gea_perf_config.h" // GEA_EMBEDDED_PERF master + per-subsystem perf flags
7
+ #include "gea/embedded.h"
8
+ #include "host/backends.h"
9
+ #include "ui/style.h"
10
+ #include "ui/tree_internal.h" // Tree::mount for the post-mount clean repaint in init()
11
+
12
+ #ifdef ESP_PLATFORM
13
+ #include "esp_timer.h"
14
+ #include "esp_log.h"
15
+ #include "services/frame_scheduler.h"
16
+ #else
17
+ #include <chrono>
18
+ #include <cstdio>
19
+ #endif
20
+
21
+ extern void __gea_top_level();
22
+ #if !defined(GEA_EMBEDDED_DIRECT_CANVAS_CONTEXT) || !GEA_EMBEDDED_DIRECT_CANVAS_CONTEXT
23
+ // Forward-declared inline runtime helper from packages/geatsc/src/targets/cpp/runtime/value.cpp.
24
+ // Drops pending microtasks WITHOUT firing them — see comment in value.cpp.
25
+ extern void gea_cpp_clear_microtasks();
26
+ #endif
27
+ namespace gea::framework::app::generated {
28
+ void drainMicrotasks();
29
+ } // namespace gea::framework::app::generated
30
+
31
+ extern "C" void gea_cycle_collection_defer_begin() __attribute__((weak));
32
+ extern "C" void gea_cycle_collection_defer_end() __attribute__((weak));
33
+
34
+ namespace gea::framework::app {
35
+
36
+ namespace {
37
+
38
+ // May be unused when all per-frame perf consumers are compiled out
39
+ // (GEA_FRAME_PHASE_TIMING==0 and GEA_FRAME_PERF_LOG==0): phaseClockUs() returns
40
+ // a constant 0 and the window-log blocks vanish, leaving no caller.
41
+ [[maybe_unused]] std::int64_t nowUs()
42
+ {
43
+ #ifdef ESP_PLATFORM
44
+ return esp_timer_get_time();
45
+ #else
46
+ using Clock = std::chrono::steady_clock;
47
+ return std::chrono::duration_cast<std::chrono::microseconds>(Clock::now().time_since_epoch()).count();
48
+ #endif
49
+ }
50
+
51
+ bool applicationFrameOwnsMountedRefresh()
52
+ {
53
+ #ifdef ESP_PLATFORM
54
+ return !gea::framework::services::FrameScheduler::vsyncDriven();
55
+ #else
56
+ return true;
57
+ #endif
58
+ }
59
+
60
+ class FrameCycleCollectionDeferral {
61
+ public:
62
+ FrameCycleCollectionDeferral()
63
+ : active_(gea_cycle_collection_defer_begin && gea_cycle_collection_defer_end)
64
+ {
65
+ if (active_) gea_cycle_collection_defer_begin();
66
+ }
67
+ ~FrameCycleCollectionDeferral() { if (active_) gea_cycle_collection_defer_end(); }
68
+
69
+ private:
70
+ bool active_;
71
+ };
72
+
73
+ } // namespace
74
+
75
+ void Application::init(int width, int height, double devicePixelRatio)
76
+ {
77
+ // Top-level construction allocates the entire native object graph. Keep the
78
+ // generated collector at a host safepoint until initialization has unwound,
79
+ // just as Application::frame does for animation callbacks.
80
+ FrameCycleCollectionDeferral deferCycleCollection;
81
+ #if defined(GEA_EMBEDDED_DIRECT_CANVAS_CONTEXT) && GEA_EMBEDDED_DIRECT_CANVAS_CONTEXT
82
+ (void)width;
83
+ (void)height;
84
+ (void)devicePixelRatio;
85
+ gea::host::resetAnimationFrameCallbacks();
86
+ gea::host::resetScheduledTimers();
87
+ __gea_top_level();
88
+ #else
89
+ if (width > 0 && height > 0) {
90
+ gea::embedded::ui::Document::setPreferredMountSize(width, height);
91
+ // `vw`/`vh` resolve against the physical viewport. Explicit CSS `px`
92
+ // lengths resolve through the target's device pixel ratio.
93
+ gea::embedded::ui::setViewportMetrics(width, height, devicePixelRatio);
94
+ }
95
+ gea::host::resetAnimationFrameCallbacks();
96
+ gea::host::resetScheduledTimers();
97
+ // DROP (don't fire) any pending microtasks left over from the previous
98
+ // app. The runtime's gea_cpp_microtask_queue is a process-wide static-inline;
99
+ // reactive-binding callbacks scheduled by the previous app's last tick
100
+ // hold node-id references and would otherwise fire on the new app's
101
+ // first frame against reused indexes — leaking styles/transforms (analog-
102
+ // clock hand rotations into breakout, balls-jsx positions into launcher
103
+ // cards). DRAINING (firing them) is unsafe at this boundary because some
104
+ // callbacks reach back into the runtime and synchronously touch display
105
+ // state, which interleaves badly with the panel/SPI flush pipeline and
106
+ // has been observed to wedge the SPI driver in mid-tx. CLEARING is safe:
107
+ // the previous tree is being destroyed by Document::clear() on the next
108
+ // line, so the dropped callbacks couldn't have done anything useful.
109
+ gea_cpp_clear_microtasks();
110
+ gea::embedded::ui::Document::instance().clear();
111
+ // Defer per-node class-style recomputes for the duration of the initial
112
+ // mount: __gea_top_level registers the stylesheet and builds the whole tree,
113
+ // and recomputing each node's class styles per setTagName/setClassName/
114
+ // appendChild is O(nodes^2 x rules). endStyleMountBatch() does one full pass.
115
+ gea::embedded::ui::beginStyleMountBatch();
116
+ __gea_top_level();
117
+ gea::embedded::ui::endStyleMountBatch();
118
+ gea::embedded::ui::Document::instance().refreshMountedIfDirty();
119
+
120
+ // The initial mount inside __gea_top_level() ran while the style batch was
121
+ // still open, so Tree::mount SKIPPED its flush (that frame is unstyled). Now
122
+ // that endStyleMountBatch() has applied styles, do one clean full re-mount to
123
+ // present the first STYLED frame. This is the guaranteed first present (it
124
+ // can't be skipped — the batch is closed) and repaints the whole framebuffer
125
+ // from settled layout, so there's no flash of unstyled content and no stale
126
+ // pre-layout pixels. One cheap full paint at startup, before the frame loop.
127
+ {
128
+ auto &tree = gea::embedded::ui::Tree::instance();
129
+ const int root = tree.mountedRoot();
130
+ if (root >= 0) tree.mount(root, tree.mountedWidth(), tree.mountedHeight());
131
+ }
132
+ #endif
133
+ }
134
+
135
+ namespace {
136
+
137
+ // Rolling-window frame stats: every kFrameLogInterval frames, log the per-phase
138
+ // µs aggregates and reset. Gives us a "where did the time go over the last ~1
139
+ // sec" view without a UI overlay or external profiler. GEA_FRAME_PERF_LOG (this
140
+ // file's "gea.perf:" line) and GEA_EMBEDDED_FRAME_SCHEDULER_PERF_LOG (the esp32
141
+ // scheduler harvest) — and the GEA_EMBEDDED_PERF master that governs both — are
142
+ // defined in gea_perf_config.h (included via app.h-side headers / directly).
143
+ //
144
+ // The per-frame phase TIMING (the nowUs() reads bracketing each phase + the stat
145
+ // accumulation in recordFramePhase) only has a consumer when one of those flags
146
+ // is on, so gate it on their OR. With both off (master off) the timing is pure
147
+ // dead work (~10 esp_timer_get_time() reads/frame feeding an accumulator nobody
148
+ // reads). NOTE: applicationFramePhaseSet() stays UNGATED below — it is a single
149
+ // relaxed atomic store feeding the frame watchdog's "app_phase=" stall report
150
+ // (see frame_scheduler.cpp), not perf machinery.
151
+ #define GEA_FRAME_PHASE_TIMING (GEA_FRAME_PERF_LOG || GEA_EMBEDDED_FRAME_SCHEDULER_PERF_LOG)
152
+
153
+ #if GEA_FRAME_PERF_LOG
154
+ // 300-frame window (~5s). Each emission is an ESP_LOGI on the frame task; at 60 frames
155
+ // it cost ~1 dropped VBlank per ~60 under TE-sync (the frame overran its ~16.7ms TE
156
+ // window during the console write). A 5s cadence keeps the rolling fps/phase readout
157
+ // while making that perturbation negligible, so vsync stays locked at the panel rate.
158
+ constexpr int kFrameLogInterval = 300;
159
+ int gFrameLogCounter = 0;
160
+ int64_t gFrameLogWindowStartUs = 0;
161
+ // The window keeps its OWN sums. The shared applicationFramePerfStats
162
+ // accumulator is owned per-frame by the esp32 frame scheduler: runFrame()
163
+ // resets it at frame start and harvests it after the app callback for the
164
+ // perf-lite line. Reading/resetting it here used to (a) show ~one frame's
165
+ // stats divided by kFrameLogInterval — near-zero busy/idle regardless of real
166
+ // load — and (b) zero the 60th frame before the scheduler's harvest.
167
+ ApplicationFramePerfStats gWindowPerfStats;
168
+ // Sum of Application::frame() entry→exit wall over the window. Splits the
169
+ // idle (= wall − busy) into in-callback non-phase overhead (appWall − busy) vs
170
+ // out-of-callback scheduler dispatch (wall − appWall) — the 0.9ms-idle hunt.
171
+ int64_t gAppFrameWallUs = 0;
172
+
173
+ void logFramePerfWindow(int64_t windowEndUs)
174
+ {
175
+ const ApplicationFramePerfStats stats = gWindowPerfStats;
176
+ const int64_t totalPhaseUs = stats.drainMicrotasksUs + stats.animationFrameUs +
177
+ stats.styleRecomputeUs + stats.documentFrameUs +
178
+ stats.refreshMountedUs;
179
+ const int64_t wallUs = windowEndUs - gFrameLogWindowStartUs;
180
+ const double frames = kFrameLogInterval;
181
+ const double fps = wallUs > 0 ? (frames * 1e6) / static_cast<double>(wallUs) : 0.0;
182
+ const double drainMs = stats.drainMicrotasksUs / 1000.0 / frames;
183
+ const double animMs = stats.animationFrameUs / 1000.0 / frames;
184
+ const double styleMs = stats.styleRecomputeUs / 1000.0 / frames;
185
+ const double docMs = stats.documentFrameUs / 1000.0 / frames;
186
+ const double refMs = stats.refreshMountedUs / 1000.0 / frames;
187
+ const double busyMs = totalPhaseUs / 1000.0 / frames;
188
+ const double wallMsPerFrame = wallUs / 1000.0 / frames;
189
+ const double idleMs = wallMsPerFrame - busyMs;
190
+ const double appWallMs = gAppFrameWallUs / 1000.0 / frames;
191
+ const double appOverheadMs = appWallMs - busyMs; // non-phase work inside the callback
192
+ const double dispatchMs = wallMsPerFrame - appWallMs; // scheduler dispatch outside the callback
193
+ #ifdef ESP_PLATFORM
194
+ ESP_LOGI("gea.perf",
195
+ "fps=%.1f wall=%.2fms busy=%.2fms idle=%.2fms(appovh=%.2f disp=%.2f) | drain=%.2f anim=%.2f style=%.2f doc=%.2f refresh=%.2f",
196
+ fps, wallMsPerFrame, busyMs, idleMs, appOverheadMs, dispatchMs, drainMs, animMs, styleMs, docMs, refMs);
197
+ #else
198
+ std::printf("[gea.perf] fps=%.1f wall=%.2fms busy=%.2fms idle=%.2fms(appovh=%.2f disp=%.2f) | drain=%.2f anim=%.2f style=%.2f doc=%.2f refresh=%.2f\n",
199
+ fps, wallMsPerFrame, busyMs, idleMs, appOverheadMs, dispatchMs, drainMs, animMs, styleMs, docMs, refMs);
200
+ #endif
201
+ gWindowPerfStats = {};
202
+ gAppFrameWallUs = 0;
203
+ }
204
+ #endif // GEA_FRAME_PERF_LOG
205
+
206
+ // Reads the frame timer ONLY when a per-frame perf consumer is compiled in (see
207
+ // GEA_FRAME_PHASE_TIMING). Otherwise returns 0, so the bracketing reads + the
208
+ // recordFramePhase() adds below constant-fold to nothing — a production build
209
+ // pays zero for phase instrumentation.
210
+ std::int64_t phaseClockUs()
211
+ {
212
+ #if GEA_FRAME_PHASE_TIMING
213
+ return nowUs();
214
+ #else
215
+ return 0;
216
+ #endif
217
+ }
218
+
219
+ // Feeds whichever per-frame perf consumers are compiled in: the shared
220
+ // accumulator harvested by the esp32 scheduler (GEA_EMBEDDED_FRAME_SCHEDULER_PERF_LOG)
221
+ // and/or this file's rolling app-perf window (GEA_FRAME_PERF_LOG). A no-op when
222
+ // neither is set.
223
+ void recordFramePhase(ApplicationFramePhase phase, int64_t durationUs)
224
+ {
225
+ #if GEA_EMBEDDED_FRAME_SCHEDULER_PERF_LOG
226
+ applicationFramePerfStatsAdd(phase, durationUs);
227
+ #endif
228
+ #if GEA_FRAME_PERF_LOG
229
+ applicationFramePerfStatsAdd(gWindowPerfStats, phase, durationUs);
230
+ #endif
231
+ #if !GEA_FRAME_PHASE_TIMING
232
+ (void)phase;
233
+ (void)durationUs;
234
+ #endif
235
+ }
236
+
237
+ } // namespace
238
+
239
+ void Application::frame(int timestampMs)
240
+ {
241
+ // Generated callbacks keep native references in ordinary C++ stack locals.
242
+ // An allocation-pressure collection inside one of those callbacks cannot see
243
+ // those roots, so defer it until every phase in this frame has unwound.
244
+ FrameCycleCollectionDeferral deferCycleCollection;
245
+ #if defined(GEA_EMBEDDED_DIRECT_CANVAS_CONTEXT) && GEA_EMBEDDED_DIRECT_CANVAS_CONTEXT
246
+ applicationFramePhaseSet(ApplicationFramePhase::DrainMicrotasks);
247
+ generated::drainMicrotasks();
248
+ applicationFramePhaseSet(ApplicationFramePhase::AnimationFrameCallbacks);
249
+ gea::host::runAnimationFrameCallbacks(static_cast<gea::host::AnimationFrameTimestamp>(timestampMs));
250
+ generated::drainMicrotasks();
251
+ applicationFramePhaseSet(ApplicationFramePhase::Idle);
252
+ #else
253
+ #if GEA_FRAME_PERF_LOG
254
+ if (gFrameLogWindowStartUs == 0) gFrameLogWindowStartUs = nowUs();
255
+ const int64_t kAppFrameStartUs = nowUs();
256
+ #endif
257
+
258
+ gea::framework::display::DisplayBackend::updateAutoRotationFromAccelerometer();
259
+
260
+ // Coalesce class-style recomputes triggered by this frame's reactive updates
261
+ // (microtask drains + rAF callbacks). Without this, one interaction that
262
+ // changes a near-root class — e.g. a weather city switch flipping the shell's
263
+ // theme class — triggers a full-tree style recompute per change on the frame
264
+ // task, freezing the UI for seconds. endStyleMountBatch() does one coalesced
265
+ // pass before layout/render; it is cheap when no class change was deferred.
266
+ gea::embedded::ui::beginStyleMountBatch();
267
+
268
+ int64_t startUs = phaseClockUs();
269
+ applicationFramePhaseSet(ApplicationFramePhase::DrainMicrotasks);
270
+ generated::drainMicrotasks();
271
+ recordFramePhase(ApplicationFramePhase::DrainMicrotasks, phaseClockUs() - startUs);
272
+
273
+ startUs = phaseClockUs();
274
+ applicationFramePhaseSet(ApplicationFramePhase::AnimationFrameCallbacks);
275
+ gea::host::runAnimationFrameCallbacks(static_cast<double>(timestampMs));
276
+ generated::drainMicrotasks();
277
+ gea::host::websocket::runCallbacks();
278
+ gea::host::rtc::runCallbacks();
279
+ gea::host::http::runRequests();
280
+ recordFramePhase(ApplicationFramePhase::AnimationFrameCallbacks, phaseClockUs() - startUs);
281
+
282
+ // Apply the coalesced style recompute for everything changed above, so layout
283
+ // and render below see final styles. Measured as its own phase: a city/theme
284
+ // class change near the root cascades a full-subtree recompute here, and it was
285
+ // previously invisible (folded into `app=` but in no phase counter).
286
+ startUs = phaseClockUs();
287
+ applicationFramePhaseSet(ApplicationFramePhase::StyleRecompute);
288
+ gea::embedded::ui::endStyleMountBatch();
289
+ recordFramePhase(ApplicationFramePhase::StyleRecompute, phaseClockUs() - startUs);
290
+
291
+ startUs = phaseClockUs();
292
+ applicationFramePhaseSet(ApplicationFramePhase::DocumentFrame);
293
+ gea::embedded::ui::Document::instance().frame(timestampMs);
294
+ recordFramePhase(ApplicationFramePhase::DocumentFrame, phaseClockUs() - startUs);
295
+
296
+ startUs = phaseClockUs();
297
+ applicationFramePhaseSet(ApplicationFramePhase::RefreshMounted);
298
+ if (applicationFrameOwnsMountedRefresh())
299
+ gea::embedded::ui::Document::instance().refreshMountedIfDirty();
300
+ recordFramePhase(ApplicationFramePhase::RefreshMounted, phaseClockUs() - startUs);
301
+ applicationFramePhaseSet(ApplicationFramePhase::Idle);
302
+
303
+ #if GEA_FRAME_PERF_LOG
304
+ gAppFrameWallUs += nowUs() - kAppFrameStartUs;
305
+ if (++gFrameLogCounter >= kFrameLogInterval) {
306
+ const int64_t windowEndUs = nowUs();
307
+ logFramePerfWindow(windowEndUs);
308
+ gFrameLogCounter = 0;
309
+ gFrameLogWindowStartUs = windowEndUs;
310
+ }
311
+ #endif
312
+ #endif
313
+ }
314
+
315
+ void Application::toggleSettings() {}
316
+
317
+ } // namespace gea::framework::app
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env node
2
+ // Shared geastack framework source manifest — the SINGLE SOURCE OF TRUTH for the
3
+ // always-linked framework C/C++ source set and include roots. Target builds
4
+ // (simulator build-web.sh, and the esp32/geaos CMake) read this instead of
5
+ // hardcoding source lists. As files move between the new packages
6
+ // (core/host/engine/elements/geaos) during the decomposition, ONLY this file
7
+ // changes and every target follows. See docs/package-architecture-plan.md.
8
+ //
9
+ // This is Node rather than shell because CMake has to read it on Windows too,
10
+ // where there is no bash: `execute_process(COMMAND bash -c ...)` failed
11
+ // outright and no ESP32 target could configure. Node is already a hard
12
+ // requirement of every path that reaches here (the CLI and geatsc are Node),
13
+ // so it costs no new dependency. gea_sources.sh remains as a thin wrapper for
14
+ // the shell callers that source it.
15
+ //
16
+ // Caller must export GEA_CORE = the @geastack/core package dir. In a normal npm
17
+ // install the CLI resolves every package through Node's npm layout and exports
18
+ // its exact directory before invoking a native build.
19
+ // ALL package include roots are on every -I line (flat `ui/…`/`host/…` spellings
20
+ // preserved) so a header resolves wherever its package put it.
21
+ //
22
+ // NOTE: app-specific sources (gea_app_entry and generated program/font/assets)
23
+ // and target shims (web_*, esp_*) are NOT here — they stay in the target build.
24
+ // This manifest is only the always-linked shared framework.
25
+
26
+ import path from 'node:path'
27
+ import { fileURLToPath } from 'node:url'
28
+
29
+ // Paths are joined with `/` rather than path.join: the output feeds CMake and
30
+ // compiler command lines, which take forward slashes on every platform, and it
31
+ // keeps the emitted strings byte-identical to what the shell manifest produced.
32
+ const join = (...parts) => parts.join('/')
33
+
34
+ function packageDirs(env) {
35
+ const read = (name) => {
36
+ const value = env[name]
37
+ if (!value) throw new Error(`${name} unset`)
38
+ return value
39
+ }
40
+ return {
41
+ core: read('GEA_CORE'),
42
+ host: read('GEA_HOST_DIR'),
43
+ engine: read('GEA_ENGINE_DIR'),
44
+ elements: read('GEA_ELEMENTS_DIR'),
45
+ geaos: read('GEA_GEAOS_PACKAGE_DIR')
46
+ }
47
+ }
48
+
49
+ export function includeFlags(env = process.env) {
50
+ const { core, host, engine, elements, geaos } = packageDirs(env)
51
+ return [
52
+ join(core, 'include'),
53
+ core,
54
+ join(host, 'include'),
55
+ host,
56
+ engine,
57
+ join(engine, 'ui'),
58
+ elements,
59
+ join(elements, 'ui'),
60
+ geaos,
61
+ join(engine, 'vendor/stb'),
62
+ join(engine, 'vendor/AnimatedGIF')
63
+ ].map((dir) => `-I${dir}`)
64
+ }
65
+
66
+ export function cSources(env = process.env) {
67
+ const { engine } = packageDirs(env)
68
+ return [join(engine, 'vendor/AnimatedGIF/AnimatedGIF.c')]
69
+ }
70
+
71
+ export function cxxSources(env = process.env) {
72
+ const { core, host, engine, elements, geaos } = packageDirs(env)
73
+ const cpp = (dir, names) => names.map((name) => join(dir, `${name}.cpp`))
74
+ return [
75
+ // @geastack/engine — machinery + intrinsics + text-input/keyboard runtime
76
+ ...cpp(join(engine, 'ui'), [
77
+ 'tree_state', 'node_lifecycle', 'display_invalidation', 'layout_snapshot',
78
+ 'absolute_leaf_refresh', 'viewport_region', 'dirty_regions', 'root_scroll_refresh',
79
+ 'node', 'view_element', 'text_element', 'image_element', 'style', 'css_atom',
80
+ 'document', 'core', 'tree_nodes', 'tree_style', 'tree_scroll_mirror', 'tree_render',
81
+ 'view', 'text', 'image_node', 'layout', 'render', 'input', 'input_render',
82
+ 'virtual_keyboard', 'canvas_element', 'canvas_store', 'tree_events'
83
+ ]),
84
+ ...cpp(engine, ['canvas', 'image_store', 'bitmap_font', 'rasterized_font', 'touch_runtime']),
85
+ // @geastack/elements — optional rich elements (engine still dispatches via NodeType until the behavior-table lands)
86
+ ...cpp(join(elements, 'ui'), ['camera_element', 'virtual_list']),
87
+ // @geastack/host — platform layer + services
88
+ ...cpp(host, ['input', 'wifi', 'bluetooth', 'geolocation']),
89
+ ...cpp(join(host, 'host'), [
90
+ 'timers', 'input', 'display', 'fetch', 'media', 'rtc', 'websocket', 'http', 'wifi',
91
+ 'ble', 'apps', 'audio', 'image', 'touch', 'imu', 'memory', 'camera', 'geolocation',
92
+ 'tile_loader'
93
+ ]),
94
+ // framework services (built on the platform abstraction — no target-specific deps)
95
+ ...cpp(join(host, 'services'), [
96
+ 'app_state', 'battery_service', 'bluetooth_service', 'diagnostics',
97
+ 'diagnostics_connection', 'diagnostics_server', 'mirror', 'network_services',
98
+ 'runtime_log'
99
+ ]),
100
+ // @geastack/core — single-app shell: frame loop + perf + event dispatch
101
+ ...cpp(core, ['app_frame_perf', 'runtime', 'events']),
102
+ // geaos — installed-application platform integration
103
+ ...cpp(geaos, ['app_manager', 'installed_apps'])
104
+ ]
105
+ }
106
+
107
+ const queries = {
108
+ 'include-flags': includeFlags,
109
+ 'c-sources': cSources,
110
+ 'cxx-sources': cxxSources
111
+ }
112
+
113
+ const invokedDirectly = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)
114
+ if (invokedDirectly) {
115
+ const query = queries[process.argv[2]]
116
+ if (!query) {
117
+ process.stderr.write(`usage: gea_sources.mjs ${Object.keys(queries).join('|')}\n`)
118
+ process.exit(2)
119
+ }
120
+ try {
121
+ process.stdout.write(`${query().join('\n')}\n`)
122
+ } catch (error) {
123
+ process.stderr.write(`${error.message}\n`)
124
+ process.exit(1)
125
+ }
126
+ }
package/gea_sources.sh ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bash
2
+ # Shell front end for the framework source manifest. The manifest itself now
3
+ # lives in gea_sources.mjs — CMake has to read it on Windows too, where there is
4
+ # no bash, so `execute_process(COMMAND bash -c ...)` failed and no ESP32 target
5
+ # could configure. Node is already a hard requirement of every path that gets
6
+ # here (the CLI and geatsc are Node), so reading it from Node costs no new
7
+ # dependency, and keeping these three function names means shell callers that
8
+ # `source` this file (simulator build-web.sh, core's own test) do not change.
9
+ #
10
+ # Caller must export GEA_CORE = the @geastack/core package dir, plus
11
+ # GEA_HOST_DIR / GEA_ENGINE_DIR / GEA_ELEMENTS_DIR / GEA_GEAOS_PACKAGE_DIR.
12
+ # gea_sources.mjs names any that are missing and exits non-zero.
13
+
14
+ # The manifest sits next to this file, so locate it from BASH_SOURCE rather than
15
+ # from GEA_CORE: sourcing still works when a caller points GEA_CORE at a
16
+ # workspace symlink whose spelling differs from this checkout's.
17
+ __GEA_SOURCES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18
+
19
+ # Run from that directory and name the manifest relatively: under Git Bash a
20
+ # native node.exe would otherwise receive a POSIX path, which only works while
21
+ # MSYS path conversion is enabled and turns into `C:\c\Users\...` when it is not.
22
+ __gea_sources_run() {
23
+ ( cd "$__GEA_SOURCES_DIR" && node ./gea_sources.mjs "$1" )
24
+ }
25
+
26
+ gea_fw_include_flags() {
27
+ __gea_sources_run include-flags
28
+ }
29
+
30
+ gea_fw_c_sources() {
31
+ __gea_sources_run c-sources
32
+ }
33
+
34
+ gea_fw_cxx_sources() {
35
+ __gea_sources_run cxx-sources
36
+ }
package/include/app.h ADDED
@@ -0,0 +1,60 @@
1
+ #pragma once
2
+
3
+ #include <cstdint>
4
+
5
+ #ifndef GEA_EMBEDDED_CSS_DEVICE_PIXEL_RATIO
6
+ #define GEA_EMBEDDED_CSS_DEVICE_PIXEL_RATIO 1.0
7
+ #endif
8
+
9
+ // The ratio the FRAME LOOP hands to Application::init, i.e. the one that makes
10
+ // `width: 100px` mean 100 or 200 physical pixels. It is deliberately separate
11
+ // from GEA_EMBEDDED_CSS_DEVICE_PIXEL_RATIO above: that one is what a BOARD
12
+ // declares, and boards declare it for the font rasteriser (the CMakeLists hands
13
+ // the same variable to geatsc as --font-device-pixel-ratio, which bakes glyph
14
+ // atlases at cssPx * ratio). Runtime::run passed a hardcoded 1.0 for layout, so
15
+ // on every board declaring 1.5 the two disagreed. Promoting the board's value to
16
+ // the layout ratio would relayout every app already authored against the 1.0
17
+ // behaviour, so the layout ratio stays 1.0 unless an APP asks for another one --
18
+ // only the app's own CSS knows which pixels its lengths are written in. Declare
19
+ // it with `gea.cssDevicePixelRatio` in the app manifest: the CLI sets this and
20
+ // the font ratio together, so the atlas and the layout agree.
21
+ #ifndef GEA_EMBEDDED_CSS_LAYOUT_DEVICE_PIXEL_RATIO
22
+ #define GEA_EMBEDDED_CSS_LAYOUT_DEVICE_PIXEL_RATIO 1.0
23
+ #endif
24
+
25
+ namespace gea::framework::app {
26
+
27
+ enum class ApplicationFramePhase : std::uint8_t {
28
+ Idle,
29
+ DrainMicrotasks,
30
+ AnimationFrameCallbacks,
31
+ StyleRecompute,
32
+ DocumentFrame,
33
+ RefreshMounted,
34
+ };
35
+
36
+ struct ApplicationFramePerfStats {
37
+ std::int64_t drainMicrotasksUs = 0;
38
+ std::int64_t animationFrameUs = 0;
39
+ std::int64_t styleRecomputeUs = 0;
40
+ std::int64_t documentFrameUs = 0;
41
+ std::int64_t refreshMountedUs = 0;
42
+ };
43
+
44
+ class Application {
45
+ public:
46
+ static void init(int width, int height, double devicePixelRatio = GEA_EMBEDDED_CSS_DEVICE_PIXEL_RATIO);
47
+ static void frame(int timestampMs);
48
+ static void toggleSettings();
49
+ };
50
+
51
+ void applicationFramePerfStatsReset();
52
+ void applicationFramePerfStatsAdd(ApplicationFramePhase phase, std::int64_t durationUs);
53
+ void applicationFramePerfStatsAdd(ApplicationFramePerfStats &stats, ApplicationFramePhase phase,
54
+ std::int64_t durationUs);
55
+ ApplicationFramePerfStats applicationFramePerfStatsRead();
56
+ void applicationFramePhaseSet(ApplicationFramePhase phase);
57
+ ApplicationFramePhase applicationFramePhaseRead();
58
+ const char *applicationFramePhaseName(ApplicationFramePhase phase);
59
+
60
+ } // namespace gea::framework::app
package/include/apps.h ADDED
@@ -0,0 +1,46 @@
1
+ #pragma once
2
+
3
+ namespace gea::framework::apps {
4
+
5
+ struct InstalledAppPlanEntry {
6
+ const char *appId = nullptr;
7
+ const char *slotLabel = nullptr;
8
+ };
9
+
10
+ class InstalledApps {
11
+ public:
12
+ static int planCount();
13
+ static const InstalledAppPlanEntry *planAt(int index);
14
+ };
15
+
16
+ class AppLauncherPlatform {
17
+ public:
18
+ virtual ~AppLauncherPlatform() = default;
19
+
20
+ virtual const char *currentInstalledAppId() = 0;
21
+ virtual bool runningAppIsLauncher(const char *launcherAppId) = 0;
22
+ virtual bool launchInstalledApp(const char *appId) = 0;
23
+ virtual void startLauncherButtonTask() {}
24
+ };
25
+
26
+ class AppManager {
27
+ public:
28
+ static constexpr const char *launcherAppId() { return "app-launcher"; }
29
+ static void setPlatform(AppLauncherPlatform *platform);
30
+ static AppLauncherPlatform *platform();
31
+ static const char *currentId();
32
+ static bool launch(const char *appId);
33
+ static bool returnRunningAppToLauncher(const char *trigger);
34
+ static bool queueSettingsToggle();
35
+ static bool shouldEnableLauncherButton();
36
+ static void startLauncherButtonTask();
37
+ };
38
+
39
+ namespace generated {
40
+
41
+ int installedAppPlanCount();
42
+ const InstalledAppPlanEntry *installedAppPlanAt(int index);
43
+
44
+ }
45
+
46
+ } // namespace gea::framework::apps
@@ -0,0 +1,90 @@
1
+ #pragma once
2
+
3
+ #ifdef __cplusplus
4
+
5
+ #include <cstddef>
6
+ #include <cstdint>
7
+ #include <string>
8
+
9
+ namespace gea::platform::audio {
10
+
11
+ enum class OscillatorType {
12
+ Sine = 0,
13
+ Square = 1,
14
+ Sawtooth = 2,
15
+ Triangle = 3,
16
+ };
17
+
18
+ using NativeAudioHandle = std::uintptr_t;
19
+
20
+ class AudioParam {
21
+ public:
22
+ explicit AudioParam(NativeAudioHandle oscillator = 0);
23
+ double value() const;
24
+ void setValue(double value);
25
+ void setValueAtTime(double value, double start_time);
26
+
27
+ private:
28
+ NativeAudioHandle oscillator_;
29
+ };
30
+
31
+ class AudioNode {
32
+ public:
33
+ NativeAudioHandle nativeId() const;
34
+
35
+ protected:
36
+ explicit AudioNode(NativeAudioHandle native);
37
+
38
+ private:
39
+ NativeAudioHandle native_;
40
+ };
41
+
42
+ class AudioDestinationNode : public AudioNode {
43
+ public:
44
+ explicit AudioDestinationNode(NativeAudioHandle native = 0);
45
+ };
46
+
47
+ class OscillatorNode : public AudioNode {
48
+ public:
49
+ explicit OscillatorNode(NativeAudioHandle native = 0);
50
+
51
+ OscillatorType type() const;
52
+ void setType(OscillatorType type);
53
+ void connect(const AudioDestinationNode &destination);
54
+ void start(double when = 0.0);
55
+ void stop(double when = 0.0);
56
+
57
+ AudioParam frequency;
58
+ };
59
+
60
+ class AudioContext {
61
+ public:
62
+ double currentTime() const;
63
+ AudioDestinationNode destination() const;
64
+ OscillatorNode createOscillator() const;
65
+ };
66
+
67
+ class AudioSystem {
68
+ public:
69
+ static AudioContext sharedContext();
70
+ static int volume();
71
+ static void setVolume(int volume_percent);
72
+ static bool playFile(const std::string &path);
73
+ static bool playPcm(const std::int16_t *samples, std::size_t sample_count, int sample_rate, int channels);
74
+ static void stopPlayback();
75
+ };
76
+
77
+ #ifdef GEA_AUDIO_DRIVER_INTERNAL
78
+ class OutputDriver {
79
+ public:
80
+ static bool open(int sample_rate, int channels, int bits_per_sample);
81
+ static bool write(const std::int16_t *pcm, std::size_t sample_count, int timeout_ms);
82
+ static void close();
83
+ static int volume();
84
+ static void setVolume(int volume_percent);
85
+ };
86
+ #endif
87
+
88
+ } // namespace gea::platform::audio
89
+
90
+ #endif