Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions contracts/spec/platforms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ export type PocketCapabilityId = CapabilityId<typeof POCKET_CAPABILITIES>;
export const POCKET_TARGETS = defineTargetRegistry<PocketCapabilityId, {
readonly psp: TargetProfile<PocketCapabilityId>;
readonly vita: TargetProfile<PocketCapabilityId>;
readonly pocketbook: TargetProfile<PocketCapabilityId>;
readonly "macos-widget": TargetProfile<PocketCapabilityId>;
}>({
psp: {
Expand Down Expand Up @@ -201,6 +202,27 @@ export const POCKET_TARGETS = defineTargetRegistry<PocketCapabilityId, {
"text.glyphs.baked",
],
},
// PocketBook e-readers (inkview): hosts/pocketbook reuses the backend-
// agnostic ui surface + the core software rasterizer. Same logical viewport
// and density as vita (480×272 @2x → a 960×544 render). physicalViewport is
// that nominal 2x surface — NOT the raw panel — so integer-fit apps validate
// (real panels vary by model, e.g. Verse 1024×758, Era Color 1264×1680, and
// are not integer multiples of 480×272); the host queries the actual panel at
// runtime and integer-fit centers the 960×544 render on it. No analog nub;
// a REAL capacitive pointer instead of the synthesized cursor. Color panels
// (Era Color) blit RGB, grayscale panels (Verse) blit Gray8.
pocketbook: {
hostAbi: 5,
platform: "pocketbook",
form: "takeover",
display: {
physicalViewport: [960, 544],
logicalViewports: [[480, 272]],
presentations: ["integer-fit"],
rasterDensity: 2,
},
capabilities: ["input.buttons", "input.touch", "text.glyphs.baked"],
},
// The flat pocket-widget shell (examples/note-widget is the stock host):
// a resizable always-on-top window whose logical viewport IS the window,
// rendered at density 2 for Retina. No nub, no synthesized cursor — the
Expand Down
7 changes: 4 additions & 3 deletions docs/STRUCTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ pocketjs/
│ ├─ wasm/ core compiled to wasm32 for web/sim hosts (standalone crate)
│ ├─ symbian/ no_std core static library for the GCCE/Symbian host (standalone crate)
│ ├─ pocket3d/ the 3D core family (bsp, cook, gu, vita) + desktop examples
│ ├─ crates/ non-3D engine crates: pocket-mod, pocket-ui-wgpu, pocket-vrm, pocket-widget
│ ├─ crates/ non-3D engine crates: pocket-mod, pocket-ui-surface, pocket-ui-wgpu, pocket-vrm, pocket-widget
│ └─ Cargo.toml the desktop workspace root (core/, wasm/, symbian/, and
│ console-toolchain crates are deliberately excluded and
│ standalone; see each crate's Cargo.toml for its toolchain)
├─ hosts/ Surfaces: every embedding of the cores
│ ├─ psp/ QuickJS + rust-psp EBOOT host
│ ├─ vita/ Vita host
│ ├─ esp32p4/ reusable ESP-IDF PPA adapter + component smoke build
│ ├─ pocketbook/ PocketBook e-reader host (inkview, standalone lone-bin crate)
│ ├─ symbian/ Nokia E7 Qt/QuickJS runtime + visible toolchain probe
│ ├─ web/ browser dev host (wasm core)
│ └─ sim/ deterministic headless simulation host (docs/DETERMINISM.md)
Expand Down Expand Up @@ -66,6 +67,6 @@ New things go where the axis says — never invent a top-level directory:
change; the `exports`/`files` maps in package.json absorb internal moves.
- **Cargo stays non-workspace where toolchains demand it**: `engine/core`,
`engine/wasm`, `engine/symbian`, `engine/backends/esp32p4-ppa`, `hosts/psp`,
`hosts/vita`, and the gu/vita 3D crates each stand alone with their own
lockfiles. `engine/Cargo.toml` is the one desktop workspace.
`hosts/vita`, `hosts/pocketbook`, and the gu/vita 3D crates each stand alone
with their own lockfiles. `engine/Cargo.toml` is the one desktop workspace.
- **Moves are `git mv`** — history stays traceable.
13 changes: 11 additions & 2 deletions engine/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
resolver = "2"
members = [
"crates/pocket-mod",
"crates/pocket-ui-surface",
"crates/pocket-ui-wgpu",
"crates/pocket-vrm",
"crates/pocket-widget",
Expand Down Expand Up @@ -40,6 +41,7 @@ repository = "https://github.com/pocket-stack/pocketjs"
pocket3d = { path = "pocket3d/crates/pocket3d" }
pocket3d-bsp = { path = "pocket3d/crates/pocket3d-bsp" }
pocket-mod = { path = "crates/pocket-mod" }
pocket-ui-surface = { path = "crates/pocket-ui-surface" }
pocket-ui-wgpu = { path = "crates/pocket-ui-wgpu" }
pocket-vrm = { path = "crates/pocket-vrm" }
pocket-widget = { path = "crates/pocket-widget" }
Expand Down
52 changes: 52 additions & 0 deletions engine/crates/pocket-mod/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,34 @@ impl Guest {
Ok(())
}

/// One guest turn with touch contacts. `touches` packs each contact as
/// `(id << 18) | (y << 9) | x` (framework/src/touch.ts): 9 bits per axis
/// (so logical coordinates must be ≤ 511), 8 bits of id, up to 8 contacts.
/// A contact present in the array is down/move this frame; absent = released.
/// Hosts without touch call [`Guest::frame`] / [`Guest::frame_with_analog`]
/// instead; this is the 3-arg `globalThis.frame(buttons, analog, touches)`
/// path for touch targets (Vita, PocketBook).
pub fn frame_with_touches(&self, buttons: u32, analog: u32, touches: &[u32]) -> Result<()> {
self.ctx.with(|ctx| -> Result<()> {
let frame: Option<Function> = ctx.globals().get("frame").ok();
if let Some(frame) = frame {
let arr = rquickjs::Array::new(ctx.clone())
.map_err(|e| anyhow!("pocket-mod: allocating touch array: {e}"))?;
for (i, t) in touches.iter().enumerate() {
arr.set(i, *t)
.map_err(|e| anyhow!("pocket-mod: setting touch {i}: {e}"))?;
}
frame
.call::<_, ()>((buttons, analog, arr))
.catch(&ctx)
.map_err(|e| anyhow!("pocket-mod: frame() threw: {e}"))?;
}
Ok(())
})?;
self.drain_jobs();
Ok(())
}

/// Drain the microtask/job queue (promise reactions). Job exceptions are
/// logged, not fatal — matching how hosts treat stray rejections.
pub fn drain_jobs(&self) {
Expand Down Expand Up @@ -269,6 +297,30 @@ mod tests {
assert_eq!(a, pocketjs_core::spec::ANALOG_CENTER);
}

#[test]
fn frame_carries_packed_touches() {
let g = Guest::new().unwrap();
g.eval(
"boot",
"globalThis.res = ''; \
globalThis.frame = (b, a, t) => { \
globalThis.res = b + ':' + (t ? t.length : 0) + ':' + (t && t[0] !== undefined ? t[0] : -1); \
};",
)
.unwrap();
// (id<<18)|(y<<9)|x — contact id 0 at logical (10, 20):
let packed = (20u32 << 9) | 10;
g.frame_with_touches(5, pocketjs_core::spec::ANALOG_CENTER, &[packed])
.unwrap();
let res: String = g.with(|ctx| ctx.globals().get("res").unwrap());
assert_eq!(res, format!("5:1:{packed}"));
// No contacts → empty array, frame still turns.
g.frame_with_touches(0, pocketjs_core::spec::ANALOG_CENTER, &[])
.unwrap();
let res: String = g.with(|ctx| ctx.globals().get("res").unwrap());
assert_eq!(res, "0:0:-1");
}

#[test]
fn exceptions_carry_js_stack() {
let g = Guest::new().unwrap();
Expand Down
13 changes: 13 additions & 0 deletions engine/crates/pocket-ui-surface/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "pocket-ui-surface"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
description = "The backend-agnostic PocketJS `ui` surface: pak feeding + HostOps for the guest, independent of any renderer"

[dependencies]
pocket-mod = { workspace = true }
pocketjs-core = { workspace = true }
anyhow = { workspace = true }
log = { workspace = true }
16 changes: 16 additions & 0 deletions engine/crates/pocket-ui-surface/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//! pocket-ui-surface — the backend-agnostic half of the PocketJS `ui` surface.
//!
//! Owns a [`pocketjs_core::Ui`] core, feeds it app paks (styles, font atlases,
//! images, sprites — the same walk as the PSP's `hosts/psp/src/pak.rs`), and
//! mounts the `ui.*` HostOps surface into a [`pocket_mod::Guest`]. It has NO
//! renderer dependency: the desktop wgpu host (`pocket-ui-wgpu`) and the
//! PocketBook e-ink host (`hosts/pocketbook`) both build on it, pairing it with
//! their own DrawList backends (docs/RUNTIMES.md — a runtime is
//! ⟨Cores, Surfaces, Guest⟩; this crate is the Surface mechanism).

mod dbg;
mod pak;
mod surface;

pub use pak::{PakEntry, find_pak, walk_pak};
pub use surface::UiSurface;
3 changes: 1 addition & 2 deletions engine/crates/pocket-ui-wgpu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ description = "The PocketJS `ui` surface on the native desktop base: pak feeding

[dependencies]
pocket3d = { workspace = true }
pocket-mod = { workspace = true }
pocket-ui-surface = { workspace = true }
pocketjs-core = { workspace = true }
wgpu = { workspace = true }
bytemuck = { workspace = true }
anyhow = { workspace = true }
log = { workspace = true }
20 changes: 9 additions & 11 deletions engine/crates/pocket-ui-wgpu/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//! pocket-ui-wgpu — the PocketJS `ui` surface on the native desktop base.
//! pocket-ui-wgpu — the PocketJS `ui` surface rendered through wgpu.
//!
//! The desktop edition of the 2D UI runtime's native half (docs/RUNTIMES.md):
//! it owns a [`pocketjs_core::Ui`] core, feeds it app paks (styles, font
//! atlases, images, sprites — same walk as the PSP's `hosts/psp/src/pak.rs`),
//! mounts the `ui.*` HostOps surface into a [`pocket_mod::Guest`], and
//! renders the core's DrawList through wgpu into any render target:
//! The desktop edition of the 2D UI runtime's native half (docs/RUNTIMES.md).
//! The backend-agnostic surface — the [`pocketjs_core::Ui`] core, pak feeding,
//! and the `ui.*` HostOps mounted into a [`pocket_mod::Guest`] — lives in
//! `pocket-ui-surface` and is re-exported here unchanged. This crate adds the
//! wgpu DrawList backend that renders that core into any render target:
//!
//! - a window at PSP resolution → the existing PocketJS demos run natively
//! on macOS (see `examples/uihost`);
Expand All @@ -14,12 +14,10 @@
//! 2D and 3D share one base: the same `pocket3d::Gpu` device drives both.

mod blit;
mod dbg;
mod pak;
mod render;
mod surface;

pub use blit::Blit;
pub use pak::{PakEntry, walk_pak};
pub use render::UiRenderer;
pub use surface::UiSurface;
// The backend-agnostic surface (UiSurface + pak walk) — re-exported so desktop
// consumers (uihost, OpenStrike) stay source-compatible after the split.
pub use pocket_ui_surface::{PakEntry, UiSurface, walk_pak};
Loading