@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.
- package/LICENSE +202 -0
- package/README.md +36 -0
- package/app_frame_perf.cpp +77 -0
- package/css.d.ts +1 -0
- package/events.cpp +67 -0
- package/gea_app_entry.cpp +317 -0
- package/gea_sources.mjs +126 -0
- package/gea_sources.sh +36 -0
- package/include/app.h +60 -0
- package/include/apps.h +46 -0
- package/include/audio.h +90 -0
- package/include/ble.h +212 -0
- package/include/bluetooth/hid_profile.h +92 -0
- package/include/camera.h +107 -0
- package/include/canvas.h +290 -0
- package/include/display.h +390 -0
- package/include/display_present.h +869 -0
- package/include/event.h +32 -0
- package/include/events.h +116 -0
- package/include/gea/embedded.h +13 -0
- package/include/gea/reactive_runtime.h +32 -0
- package/include/gea.h +14 -0
- package/include/gea_perf_config.h +96 -0
- package/include/geaos/abi.h +165 -0
- package/include/geolocation.h +126 -0
- package/include/gpio.h +61 -0
- package/include/graphics/font.h +122 -0
- package/include/i2c.h +20 -0
- package/include/image.h +156 -0
- package/include/imu.h +23 -0
- package/include/input.h +33 -0
- package/include/memory.h +52 -0
- package/include/pixel.h +907 -0
- package/include/platform/file_cache.h +18 -0
- package/include/power.h +11 -0
- package/include/runtime.h +83 -0
- package/include/services/app_runner.h +10 -0
- package/include/services/app_state.h +14 -0
- package/include/services/battery_service.h +12 -0
- package/include/services/bluetooth_service.h +13 -0
- package/include/services/diagnostics.h +76 -0
- package/include/services/diagnostics_platform.h +54 -0
- package/include/services/frame_scheduler.h +71 -0
- package/include/services/mirror.h +49 -0
- package/include/services/network_services.h +25 -0
- package/include/services/ota.h +32 -0
- package/include/services/runtime_log.h +11 -0
- package/include/services/storage_service.h +26 -0
- package/include/touch.h +38 -0
- package/include/ui/signal.h +149 -0
- package/include/wifi.h +67 -0
- package/index.d.ts +1940 -0
- package/jsx.d.ts +1 -0
- package/package.json +70 -0
- package/runtime/ble.ts +49 -0
- package/runtime/color.ts +21 -0
- package/runtime/compiler.ts +43 -0
- package/runtime/host.ts +1083 -0
- package/runtime/images.ts +129 -0
- package/runtime/primitives.ts +49 -0
- package/runtime-surface.ts +180 -0
- package/runtime.cpp +431 -0
- package/runtime.ts +34 -0
- package/scripts/build-gea-vite-geatsc.mjs +4514 -0
- package/scripts/dotenv-defines.mjs +98 -0
- package/scripts/gea-bundle-type-hints.mjs +546 -0
- package/scripts/gea-embedded-compat-staging.mjs +17 -0
- package/scripts/gea-embedded-compat-transform.mjs +427 -0
- package/scripts/gea-vite-module-graph-plugin.mjs +578 -0
- package/scripts/generate-gea-embedded-assets.mjs +296 -0
- package/scripts/generate-gea-embedded-fonts.mjs +1252 -0
- package/scripts/generate-installed-app-plan.mjs +55 -0
- package/scripts/native-object-cache.sh +244 -0
package/runtime.cpp
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#include "runtime.h"
|
|
3
|
+
|
|
4
|
+
#include "app.h"
|
|
5
|
+
#include "apps.h"
|
|
6
|
+
#include "display.h"
|
|
7
|
+
#include "input.h"
|
|
8
|
+
#include "memory.h"
|
|
9
|
+
#include "events.h"
|
|
10
|
+
#include "power.h"
|
|
11
|
+
#include "services/app_runner.h"
|
|
12
|
+
#include "services/app_state.h"
|
|
13
|
+
#include "services/battery_service.h"
|
|
14
|
+
#include "services/bluetooth_service.h"
|
|
15
|
+
#include "services/diagnostics.h"
|
|
16
|
+
#include "services/frame_scheduler.h"
|
|
17
|
+
#include "services/network_services.h"
|
|
18
|
+
#include "services/runtime_log.h"
|
|
19
|
+
#include "services/settings_surface.h"
|
|
20
|
+
#include "services/storage_service.h"
|
|
21
|
+
|
|
22
|
+
#include "host/storage.h"
|
|
23
|
+
#include "wifi.h"
|
|
24
|
+
|
|
25
|
+
#if __has_include("wifi_config.h")
|
|
26
|
+
#include "wifi_config.h"
|
|
27
|
+
#endif
|
|
28
|
+
#if __has_include("gea_embedded_app_config.h")
|
|
29
|
+
#include "gea_embedded_app_config.h"
|
|
30
|
+
#endif
|
|
31
|
+
#ifndef GEA_EMBEDDED_WIFI_SSID
|
|
32
|
+
#define GEA_EMBEDDED_WIFI_SSID ""
|
|
33
|
+
#endif
|
|
34
|
+
#ifndef GEA_EMBEDDED_WIFI_EARLY_CONNECT
|
|
35
|
+
#define GEA_EMBEDDED_WIFI_EARLY_CONNECT 0
|
|
36
|
+
#endif
|
|
37
|
+
|
|
38
|
+
#include "css/declarative.h"
|
|
39
|
+
#include "css/engine.h"
|
|
40
|
+
#include "ui/style.h"
|
|
41
|
+
#include "ui/document.h"
|
|
42
|
+
#include "ui/tree_internal.h"
|
|
43
|
+
|
|
44
|
+
namespace gea::framework {
|
|
45
|
+
|
|
46
|
+
namespace {
|
|
47
|
+
|
|
48
|
+
#if defined(GEA_EMBEDDED_DIRECT_CANVAS_CONTEXT) && GEA_EMBEDDED_DIRECT_CANVAS_CONTEXT
|
|
49
|
+
|
|
50
|
+
void run_app_frame(int timestampMs, void *context)
|
|
51
|
+
{
|
|
52
|
+
(void)context;
|
|
53
|
+
events::TouchRuntime::poll(timestampMs);
|
|
54
|
+
services::BatteryService::poll(timestampMs);
|
|
55
|
+
app::Application::frame(timestampMs);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
void dispatch_event(const events::Event &event, const RuntimeOptions &)
|
|
59
|
+
{
|
|
60
|
+
if (event.type != events::EventType::Frame) return;
|
|
61
|
+
services::FrameScheduler::FrameCallbacks frameCallbacks{};
|
|
62
|
+
frameCallbacks.frame = run_app_frame;
|
|
63
|
+
services::FrameScheduler::runFrame(frameCallbacks);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#else
|
|
67
|
+
|
|
68
|
+
void dispatch_rotary_input()
|
|
69
|
+
{
|
|
70
|
+
const int delta = input::consumeRotaryDelta();
|
|
71
|
+
if (delta == 0) return;
|
|
72
|
+
// Rotary is a document-level event — no element target, no tree walk.
|
|
73
|
+
gea::embedded::ui::dispatchDocumentRotary(delta);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Hardware-button key presses (boards without touch — e.g. the e-paper's
|
|
77
|
+
// BOOT/PWR buttons) queued via input::queueKeyDown. A focused <input> gets the
|
|
78
|
+
// key first through its per-node keydown listener; then the document-level
|
|
79
|
+
// keydown handler runs (hardware buttons / app-level shortcuts bound on a
|
|
80
|
+
// non-input element), unless the input consumed it.
|
|
81
|
+
void dispatch_key_input()
|
|
82
|
+
{
|
|
83
|
+
auto &tree = gea::embedded::ui::Tree::instance();
|
|
84
|
+
for (int keyCode = input::consumeKeyDown(); keyCode != 0; keyCode = input::consumeKeyDown()) {
|
|
85
|
+
events::PointerEvent event{};
|
|
86
|
+
event.type = events::PointerEventType::KeyDown;
|
|
87
|
+
event.keyCode = keyCode;
|
|
88
|
+
event.bubbles = true;
|
|
89
|
+
event.cancelable = true;
|
|
90
|
+
|
|
91
|
+
const int inputId = tree.activeInputId();
|
|
92
|
+
bool stopped = false;
|
|
93
|
+
if (inputId >= 0) {
|
|
94
|
+
event.targetId = inputId;
|
|
95
|
+
event.currentTargetId = inputId;
|
|
96
|
+
tree.dispatchEvent(event);
|
|
97
|
+
stopped = event.propagationStopped;
|
|
98
|
+
}
|
|
99
|
+
if (!stopped) {
|
|
100
|
+
event.propagationStopped = false;
|
|
101
|
+
gea::embedded::ui::dispatchDocumentKeyDown(event);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
void run_app_frame(int timestampMs, void *context)
|
|
107
|
+
{
|
|
108
|
+
(void)context;
|
|
109
|
+
const uint32_t nowMs = static_cast<uint32_t>(timestampMs);
|
|
110
|
+
// Low-rate platform work shares the runtime event loop. This keeps input and
|
|
111
|
+
// battery observation deterministic without dedicating several mostly-idle
|
|
112
|
+
// FreeRTOS tasks (and their stacks) to polling.
|
|
113
|
+
events::TouchRuntime::poll(static_cast<int>(nowMs));
|
|
114
|
+
services::BatteryService::poll(static_cast<int>(nowMs));
|
|
115
|
+
// A firmware image contains one application, mounted once before the frame
|
|
116
|
+
// loop. Start its declarative animations on the first frame.
|
|
117
|
+
static bool animationsScanned = false;
|
|
118
|
+
if (!animationsScanned) {
|
|
119
|
+
animationsScanned = true;
|
|
120
|
+
gea::css::DeclarativeAnimations::scanAndStart(nowMs);
|
|
121
|
+
gea::embedded::ui::StyleSheet::instance().startCssAnimations(nowMs);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
gea::css::AnimationEngine::instance().tick(nowMs);
|
|
125
|
+
// Render-only while settings overlays the frozen application.
|
|
126
|
+
const bool refreshOnly = SettingsSurface::instance().active();
|
|
127
|
+
if (refreshOnly)
|
|
128
|
+
gea::embedded::ui::Document::instance().refreshMountedIfDirty();
|
|
129
|
+
else {
|
|
130
|
+
dispatch_rotary_input();
|
|
131
|
+
dispatch_key_input();
|
|
132
|
+
app::Application::frame(timestampMs);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
void dispatch_event(const events::Event &event, const RuntimeOptions &options)
|
|
137
|
+
{
|
|
138
|
+
switch (event.type) {
|
|
139
|
+
case events::EventType::Touch: {
|
|
140
|
+
if (SettingsSurface::instance().active()) { // settings is modal: taps go to it
|
|
141
|
+
if (event.touchPhase == events::TouchPhase::Up)
|
|
142
|
+
SettingsSurface::instance().handleTap(event.x, event.y);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
services::AppState::lock();
|
|
146
|
+
const bool touchChangedScene = events::TouchRuntime::dispatchEvent(event);
|
|
147
|
+
services::AppState::unlock();
|
|
148
|
+
// Input-triggered render: a touch that actually ran handlers (down / real
|
|
149
|
+
// move / up) likely changed the scene, so post a Frame now instead of
|
|
150
|
+
// letting the render wait for the timer's next tick. Without this, a
|
|
151
|
+
// finger-down drag updates state here but only paints on the timer cadence
|
|
152
|
+
// — a multi-ms idle gap per frame that drops a full-screen pan from ~60 to
|
|
153
|
+
// ~42fps (finger-up momentum has no touch events, so it already renders
|
|
154
|
+
// back-to-back via the overrun catch-up). Redundant moves (finger held
|
|
155
|
+
// still) return false and post nothing — no busy-render on a static hold —
|
|
156
|
+
// and the frame loop's drainFramesAndCheckInput discards duplicate posts.
|
|
157
|
+
// When the panel TE is the single frame clock, do NOT post a touch-driven frame:
|
|
158
|
+
// it would render off-TE (tearing, since the present no longer waits for VBlank)
|
|
159
|
+
// and add a second producer. The touch handler already updated pan state; the next
|
|
160
|
+
// TE edge (≤16.6ms) renders it. Only post the extra frame in the non-TE path.
|
|
161
|
+
if (touchChangedScene && !services::FrameScheduler::vsyncDriven()) {
|
|
162
|
+
events::Event frameEvent{};
|
|
163
|
+
frameEvent.type = events::EventType::Frame;
|
|
164
|
+
services::FrameScheduler::sendEvent(frameEvent, 0);
|
|
165
|
+
}
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
case events::EventType::Frame: {
|
|
170
|
+
services::FrameScheduler::FrameCallbacks frameCallbacks{};
|
|
171
|
+
frameCallbacks.frame = run_app_frame;
|
|
172
|
+
services::FrameScheduler::runFrame(frameCallbacks);
|
|
173
|
+
// Graceful overrun: if the frame blew the 16ms budget, run its successor
|
|
174
|
+
// back-to-back instead of waiting for the next timer tick (which would snap
|
|
175
|
+
// the overrun to a 30fps cliff). drainFramesAndCheckInput() discards the
|
|
176
|
+
// timer's redundant frame posts and reports the instant real input (touch)
|
|
177
|
+
// is queued.
|
|
178
|
+
// Keep rendering back-to-back while EITHER the last frame overran (momentum
|
|
179
|
+
// / heavy animation, as before) OR a finger is down and moved recently. The
|
|
180
|
+
// second case makes a finger-down drag self-drive at the render rate the way
|
|
181
|
+
// momentum does: without it the loop renders one frame then blocks for the
|
|
182
|
+
// next touch/timer event (~6ms idle per frame), which pinned a full-screen
|
|
183
|
+
// finger-down pan to ~42fps while finger-up momentum ran ~60. The touch
|
|
184
|
+
// driver only emits a move event when the finger position changes and only
|
|
185
|
+
// re-arms after the previous one is consumed, so after each frame the next
|
|
186
|
+
// move can be up to a poll-interval away — rendering on that cadence stalls.
|
|
187
|
+
// Driving the loop ourselves reads the latest pan every frame instead.
|
|
188
|
+
// A finger held still posts no moves, so gestureActiveWithin() lapses to
|
|
189
|
+
// false after the window and the loop quiesces — no busy-render on a hold.
|
|
190
|
+
for (;;) {
|
|
191
|
+
const bool catchUp = services::FrameScheduler::takeCatchUpRequest();
|
|
192
|
+
const bool activeDrag =
|
|
193
|
+
events::TouchRuntime::gestureActiveWithin(services::FrameScheduler::nowMs(), 50);
|
|
194
|
+
// TE single-clock: never run a frame back-to-back here — the panel TE posts the
|
|
195
|
+
// next frame exactly one VBlank later, which is the whole point. Back-to-back
|
|
196
|
+
// would render off-TE (tearing) and re-introduce a second cadence.
|
|
197
|
+
if (services::FrameScheduler::vsyncDriven() || (!catchUp && !activeDrag)) break;
|
|
198
|
+
// Service any queued input INLINE (touch dispatch is microseconds) and
|
|
199
|
+
// keep going, instead of returning to the timer-paced outer loop.
|
|
200
|
+
// drainFramesAndCheckInput() leaves a non-Frame event at the queue
|
|
201
|
+
// front, so receiveEvent() returns it immediately without blocking.
|
|
202
|
+
while (services::FrameScheduler::drainFramesAndCheckInput()) {
|
|
203
|
+
events::Event pending{};
|
|
204
|
+
if (!services::FrameScheduler::receiveEvent(&pending)) break;
|
|
205
|
+
dispatch_event(pending, options);
|
|
206
|
+
}
|
|
207
|
+
services::FrameScheduler::runFrame(frameCallbacks);
|
|
208
|
+
}
|
|
209
|
+
// Persist any localStorage writes made this frame (or from a store's
|
|
210
|
+
// init() on the PSRAM-stack task since the last frame). This is the only
|
|
211
|
+
// place flash is touched for localStorage — always the main frame task.
|
|
212
|
+
gea::host::Storage.flushPending();
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
case events::EventType::Timeout:
|
|
217
|
+
break;
|
|
218
|
+
|
|
219
|
+
case events::EventType::SettingsToggle:
|
|
220
|
+
services::AppState::lock();
|
|
221
|
+
SettingsSurface::instance().toggle();
|
|
222
|
+
services::AppState::unlock();
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
#endif
|
|
228
|
+
|
|
229
|
+
} // namespace
|
|
230
|
+
|
|
231
|
+
void Runtime::runNativeBoot()
|
|
232
|
+
{
|
|
233
|
+
// An app with native hardware of its own (audio, USB host, sensors) brings
|
|
234
|
+
// it up here, before the display framebuffer and the radios take the
|
|
235
|
+
// contiguous internal RAM they need. Idempotent, so a target may run it on
|
|
236
|
+
// its bring-up task and run() below then finds nothing left to do.
|
|
237
|
+
static bool done = false;
|
|
238
|
+
if (done) return;
|
|
239
|
+
done = true;
|
|
240
|
+
if (gea_app_native_boot) gea_app_native_boot();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Everything that has to happen before the first frame, split out of run() so a
|
|
244
|
+
// target can run it on a DIFFERENT task than the event loop. It reads and writes
|
|
245
|
+
// flash -- the SPIFFS mount, the localStorage restore, NVS from the radios --
|
|
246
|
+
// and a flash operation disables the cache, which trips
|
|
247
|
+
// esp_task_stack_is_sane_cache_disabled() for any task whose stack lives in
|
|
248
|
+
// external RAM. So a board that puts the loop on a PSRAM stack (the only way to
|
|
249
|
+
// keep 20 KB of internal DRAM free on a board whose app already owns most of it)
|
|
250
|
+
// calls boot() from its bring-up task first, and the loop task's run() then
|
|
251
|
+
// finds the work done. Idempotent, and single-shot in both outcomes: a target
|
|
252
|
+
// that only ever calls run() behaves exactly as before.
|
|
253
|
+
namespace {
|
|
254
|
+
// The app's own reserve, taken from inside gea_app_native_boot at a moment only
|
|
255
|
+
// the app knows. File-scope because boot() takes it before the hook runs and
|
|
256
|
+
// releases it after, and the hook has no runtime handle to pass it back through.
|
|
257
|
+
void *g_appDisplayReserve = nullptr;
|
|
258
|
+
} // namespace
|
|
259
|
+
|
|
260
|
+
void Runtime::holdDisplayReserve(void *reserve)
|
|
261
|
+
{
|
|
262
|
+
if (g_appDisplayReserve == reserve) return;
|
|
263
|
+
gea::platform::memory::Memory::releaseInternalDma(g_appDisplayReserve);
|
|
264
|
+
g_appDisplayReserve = reserve;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
namespace {
|
|
268
|
+
void releaseDisplayReserves(void *runtimeReserve)
|
|
269
|
+
{
|
|
270
|
+
gea::platform::memory::Memory::releaseInternalDma(runtimeReserve);
|
|
271
|
+
gea::platform::memory::Memory::releaseInternalDma(g_appDisplayReserve);
|
|
272
|
+
g_appDisplayReserve = nullptr;
|
|
273
|
+
}
|
|
274
|
+
} // namespace
|
|
275
|
+
|
|
276
|
+
bool Runtime::boot(const RuntimeOptions &options)
|
|
277
|
+
{
|
|
278
|
+
static bool done = false;
|
|
279
|
+
static bool ok = false;
|
|
280
|
+
if (done) return ok;
|
|
281
|
+
done = true;
|
|
282
|
+
// Held across the app's hook, released just before Display::init() below.
|
|
283
|
+
// See GEA_EMBEDDED_DISPLAY_INTERNAL_RESERVE_BYTES in runtime.h.
|
|
284
|
+
void *displayReserve =
|
|
285
|
+
gea::platform::memory::Memory::reserveInternalDma(GEA_EMBEDDED_DISPLAY_INTERNAL_RESERVE_BYTES);
|
|
286
|
+
runNativeBoot();
|
|
287
|
+
#if !defined(GEA_EMBEDDED_DIRECT_CANVAS_CONTEXT) || !GEA_EMBEDDED_DIRECT_CANVAS_CONTEXT
|
|
288
|
+
services::HeapProbe::log("runtime:start");
|
|
289
|
+
services::StackProbe::logCurrentTask("runtime:start");
|
|
290
|
+
#endif
|
|
291
|
+
#if defined(GEA_EMBEDDED_DIRECT_CANVAS_CONTEXT) && GEA_EMBEDDED_DIRECT_CANVAS_CONTEXT
|
|
292
|
+
if (!services::AppState::init()) return false;
|
|
293
|
+
auto eventQueue = services::FrameScheduler::createEventQueue();
|
|
294
|
+
if (!eventQueue) return false;
|
|
295
|
+
releaseDisplayReserves(displayReserve);
|
|
296
|
+
if (!gea::platform::display::Display::init()) return false;
|
|
297
|
+
// The layout ratio, not the board's font ratio -- see the comment on
|
|
298
|
+
// GEA_EMBEDDED_CSS_LAYOUT_DEVICE_PIXEL_RATIO in app.h. It defaults to 1.0, so
|
|
299
|
+
// this is the same call the hardcoded literal made; an app that declares
|
|
300
|
+
// `gea.cssDevicePixelRatio` gets its own value here and in the font atlas.
|
|
301
|
+
app::Application::init(options.width, options.height, GEA_EMBEDDED_CSS_LAYOUT_DEVICE_PIXEL_RATIO);
|
|
302
|
+
if (!gea::platform::display::Display::start()) return false;
|
|
303
|
+
services::FrameScheduler::start(eventQueue);
|
|
304
|
+
#else
|
|
305
|
+
if (!services::StorageService::init()) return false;
|
|
306
|
+
// Restore persisted localStorage into its RAM mirror once, here on the
|
|
307
|
+
// bring-up task, whose stack can touch flash — so app code can read
|
|
308
|
+
// localStorage from anywhere, including a store's init().
|
|
309
|
+
gea::host::Storage.load();
|
|
310
|
+
services::HeapProbe::log("runtime:after_storage");
|
|
311
|
+
if (!services::AppState::init()) return false;
|
|
312
|
+
services::HeapProbe::log("runtime:after_app_state");
|
|
313
|
+
|
|
314
|
+
#if !GEA_EMBEDDED_NO_DISPLAY
|
|
315
|
+
auto eventQueue = services::FrameScheduler::createEventQueue();
|
|
316
|
+
if (!eventQueue) return false;
|
|
317
|
+
services::HeapProbe::log("runtime:after_frame_queue");
|
|
318
|
+
#endif
|
|
319
|
+
|
|
320
|
+
// BLE is opt-in but must be preinited EARLY for apps that use it. The BT
|
|
321
|
+
// controller needs a large (~24-41 KB) contiguous block of internal DRAM, and
|
|
322
|
+
// on a PSRAM display board the only moment that block is reliably available is
|
|
323
|
+
// here at boot — before the app's JSX mount + display framebuffers fragment
|
|
324
|
+
// internal RAM. If we wait for the lazy first-use path (HidServer::init's
|
|
325
|
+
// preinit()), the controller's allocation fails (OOM) and BLE is silently
|
|
326
|
+
// skipped → the device never advertises. preinitForApp() is gated on
|
|
327
|
+
// GEA_EMBEDDED_APP_USES_BLE is derived from the application. Non-BLE images omit the controller source and component
|
|
328
|
+
// entirely, so they pay neither its static pools nor its boot allocation.
|
|
329
|
+
services::BluetoothService::preinitForApp();
|
|
330
|
+
services::HeapProbe::log("runtime:after_preinit");
|
|
331
|
+
|
|
332
|
+
gea::platform::power::Power::init();
|
|
333
|
+
services::HeapProbe::log("runtime:after_power");
|
|
334
|
+
releaseDisplayReserves(displayReserve);
|
|
335
|
+
// A board that declares no panel and no canvas has no surface to render to,
|
|
336
|
+
// so nothing here is constructed: no framebuffers, no app tree, no frame
|
|
337
|
+
// loop. This is not a display that is switched off -- it is a board whose
|
|
338
|
+
// hardware the firmware takes at its word, the same way an absent PMIC means
|
|
339
|
+
// no PMIC driver. Everything the app starts from its native boot hook, which
|
|
340
|
+
// ran above, is untouched.
|
|
341
|
+
#if !GEA_EMBEDDED_NO_DISPLAY
|
|
342
|
+
if (!gea::platform::display::Display::init()) return false;
|
|
343
|
+
services::HeapProbe::log("runtime:after_display_init");
|
|
344
|
+
#endif
|
|
345
|
+
|
|
346
|
+
#if GEA_EMBEDDED_WIFI_EARLY_CONNECT
|
|
347
|
+
if (GEA_EMBEDDED_WIFI_SSID[0] != '\0') {
|
|
348
|
+
// Both radios need large contiguous internal allocations, and BLE has the
|
|
349
|
+
// stricter one-shot controller allocation, so it is claimed first above.
|
|
350
|
+
// WiFi comes up AFTER Display::init(), not before it. The elastic-RAM
|
|
351
|
+
// arbiter is one-directional: the display yields staging to the radios
|
|
352
|
+
// (reserveInternal) and the radios never give any back. Starting WiFi first
|
|
353
|
+
// therefore left the panel with a few hundred bytes of DMA-capable RAM, no
|
|
354
|
+
// flush staging, and a dark screen — while the framebuffer rendered
|
|
355
|
+
// correctly in PSRAM the whole time. Display::init() now claims its floor
|
|
356
|
+
// before this line, and this bring-up shrinks it the designed way.
|
|
357
|
+
services::DiagnosticsServer::print("Starting WiFi after display init\n");
|
|
358
|
+
network::wifi().init();
|
|
359
|
+
}
|
|
360
|
+
#endif
|
|
361
|
+
|
|
362
|
+
if (!events::TouchRuntime::start()) return false;
|
|
363
|
+
services::BatteryService::update();
|
|
364
|
+
services::BatteryService::start();
|
|
365
|
+
services::HeapProbe::log("runtime:after_input_battery");
|
|
366
|
+
|
|
367
|
+
services::NetworkServicesOptions networkOptions{};
|
|
368
|
+
#ifdef GEA_EMBEDDED_DIAGNOSTICS_ENABLED
|
|
369
|
+
// Opt in to the TCP diagnostics server (port 8081, channel 1 = log stream).
|
|
370
|
+
// It defaults off because it costs a ~6 KiB task, and the default assumed
|
|
371
|
+
// logs and screenshots come over USB. A board that has the DRAM to spare
|
|
372
|
+
// gets device logs with no cable attached -- which is the only way to see a
|
|
373
|
+
// boot log at all once the USB console is gone.
|
|
374
|
+
networkOptions.diagnosticsEnabled = true;
|
|
375
|
+
#endif
|
|
376
|
+
#ifdef GEA_EMBEDDED_DIAGNOSTICS_DISABLED
|
|
377
|
+
// Production lockdown: don't start the diagnostics server (diag_srv TCP
|
|
378
|
+
// task + the esp_log_set_vprintf log-ring sink). OTA below stays enabled.
|
|
379
|
+
networkOptions.diagnosticsEnabled = false;
|
|
380
|
+
#endif
|
|
381
|
+
bool networkReady = services::NetworkServices::start(networkOptions);
|
|
382
|
+
services::HeapProbe::log("runtime:after_network");
|
|
383
|
+
|
|
384
|
+
services::RuntimeLog::printRuntimeBanner();
|
|
385
|
+
#if GEA_EMBEDDED_NO_DISPLAY
|
|
386
|
+
(void)options;
|
|
387
|
+
services::RuntimeLog::appStarted(networkReady);
|
|
388
|
+
#else
|
|
389
|
+
if (!services::AppRunner::initApplication(options.width, options.height)) return false;
|
|
390
|
+
services::HeapProbe::log("runtime:after_app_init");
|
|
391
|
+
services::StackProbe::logCurrentTask("runtime:after_app_init");
|
|
392
|
+
|
|
393
|
+
if (!gea::platform::display::Display::start()) return false;
|
|
394
|
+
services::HeapProbe::log("runtime:after_display_start");
|
|
395
|
+
services::StackProbe::logCurrentTask("runtime:after_display_start");
|
|
396
|
+
|
|
397
|
+
services::FrameScheduler::start(eventQueue);
|
|
398
|
+
services::HeapProbe::log("runtime:after_frame_start");
|
|
399
|
+
services::StackProbe::logCurrentTask("runtime:after_frame_start");
|
|
400
|
+
services::HeapTaskSummary::log("runtime:after_frame_start");
|
|
401
|
+
services::RuntimeLog::appStarted(networkReady);
|
|
402
|
+
#endif
|
|
403
|
+
|
|
404
|
+
#endif
|
|
405
|
+
ok = true;
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
void Runtime::run(const RuntimeOptions &options)
|
|
410
|
+
{
|
|
411
|
+
// A target that runs the loop on its own task calls boot() first, from a task
|
|
412
|
+
// whose stack can touch flash; this call then returns that recorded outcome
|
|
413
|
+
// without repeating any of the work.
|
|
414
|
+
if (!boot(options)) return;
|
|
415
|
+
#if GEA_EMBEDDED_NO_DISPLAY
|
|
416
|
+
// No surface means no frame queue, so there is no event to wait on and
|
|
417
|
+
// nothing to dispatch. The loop below would find receiveEvent() failing
|
|
418
|
+
// immediately against a queue that was never created and spin the core,
|
|
419
|
+
// which starves whatever else shares it. boot() was the work; return.
|
|
420
|
+
(void)options;
|
|
421
|
+
#else
|
|
422
|
+
while (true) {
|
|
423
|
+
events::Event event{};
|
|
424
|
+
if (services::FrameScheduler::receiveEvent(&event)) {
|
|
425
|
+
dispatch_event(event, options);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
#endif
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
} // namespace gea::framework
|
package/runtime.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export * from './runtime-surface'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The JSX tag set, restated on the *runtime* entrypoint so an app that
|
|
5
|
+
* augments `@geastack/core` reaches it.
|
|
6
|
+
*
|
|
7
|
+
* The package answers `"."` with two files -- `types: ./index.d.ts` for an
|
|
8
|
+
* editor, `default: ./runtime.ts` for anything that actually loads the
|
|
9
|
+
* library, this file. A build pipeline that compiles the framework from
|
|
10
|
+
* source (geatsc, and the vite plugin) resolves the specifier to THIS module,
|
|
11
|
+
* so an app's `declare module '@geastack/core' { interface
|
|
12
|
+
* GeaIntrinsicElements { 'glass-pane': ... } }` augments this module -- while
|
|
13
|
+
* `JSX.IntrinsicElements` was wired, in `index.d.ts`, to *that* module's
|
|
14
|
+
* interface. One package, two module identities, and the app's own tags
|
|
15
|
+
* landing in the one the JSX namespace never reads: every use of a custom tag
|
|
16
|
+
* is then `Property 'glass-pane' does not exist on type
|
|
17
|
+
* 'JSX.IntrinsicElements'`, which is a fact about which file the specifier
|
|
18
|
+
* resolved to and not about the app.
|
|
19
|
+
*
|
|
20
|
+
* Extending the declaration file's own interface rather than re-listing its
|
|
21
|
+
* tags keeps `index.d.ts` the single authority for what the framework itself
|
|
22
|
+
* ships; this adds only the seam an augmentation needs. The two global
|
|
23
|
+
* `JSX.IntrinsicElements` declarations merge, so a program that resolved the
|
|
24
|
+
* package the other way loses nothing.
|
|
25
|
+
*/
|
|
26
|
+
import type { GeaIntrinsicElements as FrameworkIntrinsicElements } from './index'
|
|
27
|
+
|
|
28
|
+
export interface GeaIntrinsicElements extends FrameworkIntrinsicElements {}
|
|
29
|
+
|
|
30
|
+
declare global {
|
|
31
|
+
namespace JSX {
|
|
32
|
+
interface IntrinsicElements extends GeaIntrinsicElements {}
|
|
33
|
+
}
|
|
34
|
+
}
|