@tishlang/tish 1.10.0 → 1.12.0
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/bin/tish +0 -0
- package/crates/tish/Cargo.toml +1 -1
- package/crates/tish/src/cli_help.rs +9 -1
- package/crates/tish/src/main.rs +55 -3
- package/crates/tish_build_utils/src/lib.rs +33 -10
- package/crates/tish_builtins/src/math.rs +7 -0
- package/crates/tish_builtins/src/string.rs +26 -0
- package/crates/tish_compile/src/codegen.rs +207 -26
- package/crates/tish_compile/src/lib.rs +19 -9
- package/crates/tish_compile/src/resolve.rs +185 -3
- package/crates/tish_core/src/value.rs +21 -0
- package/crates/tish_cranelift/src/link.rs +1 -1
- package/crates/tish_native/src/build.rs +88 -19
- package/crates/tish_native/src/config.rs +48 -0
- package/crates/tish_native/src/lib.rs +48 -5
- package/crates/tish_runtime/src/lib.rs +9 -1
- package/crates/tish_runtime/src/timers.rs +12 -7
- package/crates/tish_ui/src/lib.rs +5 -4
- package/crates/tish_ui/src/runtime/hooks.rs +119 -23
- package/crates/tish_ui/src/runtime/mod.rs +7 -26
- package/crates/tish_vm/src/vm.rs +40 -0
- package/crates/tish_wasm/src/lib.rs +27 -0
- package/crates/tish_wasm_runtime/Cargo.toml +6 -0
- package/crates/tish_wasm_runtime/src/gpu.rs +413 -0
- package/crates/tish_wasm_runtime/src/lib.rs +5 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- package/platform/win32-x64/tish.exe +0 -0
|
@@ -5,6 +5,8 @@ use std::cell::{Cell, RefCell};
|
|
|
5
5
|
use std::collections::HashMap;
|
|
6
6
|
use std::rc::Rc;
|
|
7
7
|
|
|
8
|
+
use std::sync::Arc;
|
|
9
|
+
|
|
8
10
|
use tishlang_core::{ObjectMap, Value, VmRef};
|
|
9
11
|
|
|
10
12
|
use super::Host;
|
|
@@ -18,9 +20,10 @@ pub const LEGACY_ROOT_ID: RootId = 1;
|
|
|
18
20
|
thread_local! {
|
|
19
21
|
static HOOKS: RefCell<HashMap<RootId, HookState>> = RefCell::new(HashMap::new());
|
|
20
22
|
static CURRENT_ROOT: Cell<Option<RootId>> = Cell::new(None);
|
|
21
|
-
static HOSTS: RefCell<HashMap<RootId, Box<dyn Host
|
|
23
|
+
static HOSTS: RefCell<HashMap<RootId, Rc<RefCell<Box<dyn Host>>>>> = RefCell::new(HashMap::new());
|
|
22
24
|
static NEXT_DYNAMIC_ROOT_ID: Cell<RootId> = Cell::new(2);
|
|
23
25
|
static IN_FLUSH: Cell<bool> = Cell::new(false);
|
|
26
|
+
static IN_EFFECT_FLUSH: Cell<bool> = Cell::new(false);
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
/// Allocate an id for an additional in-process window (starts at 2; 1 is legacy primary).
|
|
@@ -42,7 +45,8 @@ fn ensure_hook_entry(root_id: RootId) {
|
|
|
42
45
|
pub fn install_host_for_root(root_id: RootId, host: Box<dyn Host>) {
|
|
43
46
|
ensure_hook_entry(root_id);
|
|
44
47
|
HOSTS.with(|h| {
|
|
45
|
-
h.borrow_mut()
|
|
48
|
+
h.borrow_mut()
|
|
49
|
+
.insert(root_id, Rc::new(RefCell::new(host)));
|
|
46
50
|
});
|
|
47
51
|
}
|
|
48
52
|
|
|
@@ -77,8 +81,10 @@ pub fn unregister_root(root_id: RootId) {
|
|
|
77
81
|
|
|
78
82
|
pub fn with_host_for_root<R>(root_id: RootId, f: impl FnOnce(&mut dyn Host) -> R) -> Option<R> {
|
|
79
83
|
HOSTS.with(|c| {
|
|
80
|
-
let
|
|
81
|
-
m.
|
|
84
|
+
let m = c.borrow();
|
|
85
|
+
let host = m.get(&root_id)?;
|
|
86
|
+
let mut host = host.try_borrow_mut().ok()?;
|
|
87
|
+
Some(f(host.as_mut()))
|
|
82
88
|
})
|
|
83
89
|
}
|
|
84
90
|
|
|
@@ -128,6 +134,11 @@ pub struct HookState {
|
|
|
128
134
|
effect_cells: Rc<RefCell<Vec<EffectCell>>>,
|
|
129
135
|
effect_cursor: usize,
|
|
130
136
|
pending_effects: Rc<RefCell<Vec<PendingEffect>>>,
|
|
137
|
+
layout_effect_cells: Rc<RefCell<Vec<EffectCell>>>,
|
|
138
|
+
layout_effect_cursor: usize,
|
|
139
|
+
pending_layout_effects: Rc<RefCell<Vec<PendingEffect>>>,
|
|
140
|
+
ref_slots: Rc<RefCell<Vec<Value>>>,
|
|
141
|
+
ref_cursor: usize,
|
|
131
142
|
}
|
|
132
143
|
|
|
133
144
|
impl Default for HookState {
|
|
@@ -143,6 +154,11 @@ impl Default for HookState {
|
|
|
143
154
|
effect_cells: Rc::new(RefCell::new(Vec::new())),
|
|
144
155
|
effect_cursor: 0,
|
|
145
156
|
pending_effects: Rc::new(RefCell::new(Vec::new())),
|
|
157
|
+
layout_effect_cells: Rc::new(RefCell::new(Vec::new())),
|
|
158
|
+
layout_effect_cursor: 0,
|
|
159
|
+
pending_layout_effects: Rc::new(RefCell::new(Vec::new())),
|
|
160
|
+
ref_slots: Rc::new(RefCell::new(Vec::new())),
|
|
161
|
+
ref_cursor: 0,
|
|
146
162
|
}
|
|
147
163
|
}
|
|
148
164
|
}
|
|
@@ -160,11 +176,17 @@ fn run_all_effect_cleanups(cells: &RefCell<Vec<EffectCell>>) {
|
|
|
160
176
|
impl HookState {
|
|
161
177
|
pub fn reset_for_new_root(&mut self) {
|
|
162
178
|
run_all_effect_cleanups(self.effect_cells.as_ref());
|
|
179
|
+
run_all_effect_cleanups(self.layout_effect_cells.as_ref());
|
|
163
180
|
self.effect_cells.borrow_mut().clear();
|
|
181
|
+
self.layout_effect_cells.borrow_mut().clear();
|
|
164
182
|
self.effect_cursor = 0;
|
|
183
|
+
self.layout_effect_cursor = 0;
|
|
165
184
|
self.pending_effects.borrow_mut().clear();
|
|
185
|
+
self.pending_layout_effects.borrow_mut().clear();
|
|
166
186
|
self.state_slots.borrow_mut().clear();
|
|
167
187
|
self.cursor = 0;
|
|
188
|
+
self.ref_slots.borrow_mut().clear();
|
|
189
|
+
self.ref_cursor = 0;
|
|
168
190
|
self.root_vnode = None;
|
|
169
191
|
self.flush_scheduled = false;
|
|
170
192
|
self.memo_cache.borrow_mut().clear();
|
|
@@ -191,6 +213,7 @@ fn memo_dep_eq(a: &Value, b: &Value) -> bool {
|
|
|
191
213
|
}
|
|
192
214
|
ab.iter().zip(bb.iter()).all(|(x, y)| memo_dep_eq(x, y))
|
|
193
215
|
}
|
|
216
|
+
(Value::Object(a), Value::Object(b)) => tishlang_core::VmRef::ptr_eq(a, b),
|
|
194
217
|
_ => false,
|
|
195
218
|
}
|
|
196
219
|
}
|
|
@@ -239,7 +262,7 @@ pub fn native_use_state(args: &[Value]) -> Value {
|
|
|
239
262
|
st.flush_scheduled = true;
|
|
240
263
|
}
|
|
241
264
|
});
|
|
242
|
-
if !IN_FLUSH.get() {
|
|
265
|
+
if !IN_FLUSH.get() && !IN_EFFECT_FLUSH.get() {
|
|
243
266
|
drain_flush_queue();
|
|
244
267
|
}
|
|
245
268
|
Value::Null
|
|
@@ -284,9 +307,29 @@ pub fn native_use_memo(args: &[Value]) -> Value {
|
|
|
284
307
|
})
|
|
285
308
|
}
|
|
286
309
|
|
|
287
|
-
/// `
|
|
288
|
-
|
|
289
|
-
|
|
310
|
+
/// `useRef(initial)` → `{ current: initial }` (stable object identity per slot).
|
|
311
|
+
pub fn native_use_ref(args: &[Value]) -> Value {
|
|
312
|
+
let initial = args.first().cloned().unwrap_or(Value::Null);
|
|
313
|
+
let root_id = root_id_for_hooks();
|
|
314
|
+
HOOKS.with(|h| {
|
|
315
|
+
let mut map = h.borrow_mut();
|
|
316
|
+
let st = map.entry(root_id).or_default();
|
|
317
|
+
let i = st.ref_cursor;
|
|
318
|
+
st.ref_cursor += 1;
|
|
319
|
+
while i >= st.ref_slots.borrow().len() {
|
|
320
|
+
let mut m = ObjectMap::default();
|
|
321
|
+
m.insert(Arc::from("current"), initial.clone());
|
|
322
|
+
st.ref_slots.borrow_mut().push(Value::object(m));
|
|
323
|
+
}
|
|
324
|
+
let out = st.ref_slots.borrow()[i].clone();
|
|
325
|
+
out
|
|
326
|
+
})
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
fn queue_effect(
|
|
330
|
+
args: &[Value],
|
|
331
|
+
layout: bool,
|
|
332
|
+
) -> Value {
|
|
290
333
|
let Some(Value::Function(effect_fn)) = args.first() else {
|
|
291
334
|
return Value::Null;
|
|
292
335
|
};
|
|
@@ -301,9 +344,22 @@ pub fn native_use_effect(args: &[Value]) -> Value {
|
|
|
301
344
|
HOOKS.with(|h| {
|
|
302
345
|
let mut map = h.borrow_mut();
|
|
303
346
|
let st = map.entry(root_id).or_default();
|
|
304
|
-
let
|
|
305
|
-
|
|
306
|
-
|
|
347
|
+
let (cursor, cells, pending) = if layout {
|
|
348
|
+
(
|
|
349
|
+
&mut st.layout_effect_cursor,
|
|
350
|
+
&st.layout_effect_cells,
|
|
351
|
+
&st.pending_layout_effects,
|
|
352
|
+
)
|
|
353
|
+
} else {
|
|
354
|
+
(
|
|
355
|
+
&mut st.effect_cursor,
|
|
356
|
+
&st.effect_cells,
|
|
357
|
+
&st.pending_effects,
|
|
358
|
+
)
|
|
359
|
+
};
|
|
360
|
+
let i = *cursor;
|
|
361
|
+
*cursor += 1;
|
|
362
|
+
let cells = cells.clone();
|
|
307
363
|
let mut cells_b = cells.borrow_mut();
|
|
308
364
|
while cells_b.len() <= i {
|
|
309
365
|
cells_b.push(EffectCell::default());
|
|
@@ -315,7 +371,7 @@ pub fn native_use_effect(args: &[Value]) -> Value {
|
|
|
315
371
|
drop(cells_b);
|
|
316
372
|
|
|
317
373
|
if should_run {
|
|
318
|
-
|
|
374
|
+
pending.borrow_mut().push(PendingEffect {
|
|
319
375
|
slot: i,
|
|
320
376
|
effect_fn: Value::Function(effect_fn),
|
|
321
377
|
new_deps: deps,
|
|
@@ -325,14 +381,29 @@ pub fn native_use_effect(args: &[Value]) -> Value {
|
|
|
325
381
|
})
|
|
326
382
|
}
|
|
327
383
|
|
|
328
|
-
|
|
384
|
+
/// `useLayoutEffect(effect, deps?)` — runs synchronously after commit, before `useEffect`.
|
|
385
|
+
pub fn native_use_layout_effect(args: &[Value]) -> Value {
|
|
386
|
+
queue_effect(args, true)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/// `useEffect(effect, deps?)` — runs `effect` after the host commits the tree; compares `deps` like `useMemo`.
|
|
390
|
+
/// If `effect` returns a function, it is called before the next run or on root teardown (`render` replacement / [`unregister_root`]).
|
|
391
|
+
pub fn native_use_effect(args: &[Value]) -> Value {
|
|
392
|
+
queue_effect(args, false)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
fn flush_pending_effects_for(
|
|
396
|
+
root_id: RootId,
|
|
397
|
+
pending_key: impl FnOnce(&mut HookState) -> &Rc<RefCell<Vec<PendingEffect>>>,
|
|
398
|
+
cells_key: impl FnOnce(&HookState) -> Rc<RefCell<Vec<EffectCell>>>,
|
|
399
|
+
) {
|
|
329
400
|
let pending: Vec<PendingEffect> = HOOKS.with(|h| {
|
|
330
401
|
h.borrow_mut()
|
|
331
402
|
.get_mut(&root_id)
|
|
332
|
-
.map(|st| std::mem::take(&mut *st.
|
|
403
|
+
.map(|st| std::mem::take(&mut *pending_key(st).borrow_mut()))
|
|
333
404
|
.unwrap_or_default()
|
|
334
405
|
});
|
|
335
|
-
let cells_rc = HOOKS.with(|h| h.borrow().get(&root_id).map(
|
|
406
|
+
let cells_rc = HOOKS.with(|h| h.borrow().get(&root_id).map(cells_key));
|
|
336
407
|
let Some(cells_rc) = cells_rc else {
|
|
337
408
|
return;
|
|
338
409
|
};
|
|
@@ -370,6 +441,26 @@ fn flush_pending_effects(root_id: RootId) {
|
|
|
370
441
|
}
|
|
371
442
|
}
|
|
372
443
|
|
|
444
|
+
fn flush_pending_layout_effects(root_id: RootId) {
|
|
445
|
+
IN_EFFECT_FLUSH.set(true);
|
|
446
|
+
flush_pending_effects_for(
|
|
447
|
+
root_id,
|
|
448
|
+
|st| &st.pending_layout_effects,
|
|
449
|
+
|st| st.layout_effect_cells.clone(),
|
|
450
|
+
);
|
|
451
|
+
IN_EFFECT_FLUSH.set(false);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
fn flush_pending_effects(root_id: RootId) {
|
|
455
|
+
IN_EFFECT_FLUSH.set(true);
|
|
456
|
+
flush_pending_effects_for(
|
|
457
|
+
root_id,
|
|
458
|
+
|st| &st.pending_effects,
|
|
459
|
+
|st| st.effect_cells.clone(),
|
|
460
|
+
);
|
|
461
|
+
IN_EFFECT_FLUSH.set(false);
|
|
462
|
+
}
|
|
463
|
+
|
|
373
464
|
fn parse_root_id_arg(args: &[Value]) -> RootId {
|
|
374
465
|
match args.first() {
|
|
375
466
|
Some(Value::Number(n)) if n.is_finite() && *n >= 1.0 && n.fract() == 0.0 => *n as u64,
|
|
@@ -416,6 +507,10 @@ pub fn schedule_flush() {
|
|
|
416
507
|
}
|
|
417
508
|
|
|
418
509
|
fn drain_flush_queue() {
|
|
510
|
+
drain_flush_queue_on_current_thread();
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
fn drain_flush_queue_on_current_thread() {
|
|
419
514
|
loop {
|
|
420
515
|
let root_id = HOOKS.with(|h| {
|
|
421
516
|
h.borrow()
|
|
@@ -440,8 +535,11 @@ fn drain_flush_queue() {
|
|
|
440
535
|
let st = map.get_mut(&root_id)?;
|
|
441
536
|
st.cursor = 0;
|
|
442
537
|
st.memo_cursor = 0;
|
|
538
|
+
st.ref_cursor = 0;
|
|
443
539
|
st.effect_cursor = 0;
|
|
540
|
+
st.layout_effect_cursor = 0;
|
|
444
541
|
st.pending_effects.borrow_mut().clear();
|
|
542
|
+
st.pending_layout_effects.borrow_mut().clear();
|
|
445
543
|
let app = st.root_app.clone()?;
|
|
446
544
|
let Value::Function(f) = app else {
|
|
447
545
|
return None;
|
|
@@ -452,18 +550,16 @@ fn drain_flush_queue() {
|
|
|
452
550
|
if let Some(f) = app_fn {
|
|
453
551
|
let tree = f(&[]);
|
|
454
552
|
HOOKS.with(|h| {
|
|
455
|
-
let
|
|
456
|
-
if let Some(st) = map.get_mut(&root_id) {
|
|
553
|
+
if let Some(st) = h.borrow_mut().get_mut(&root_id) {
|
|
457
554
|
st.root_vnode = Some(tree.clone());
|
|
458
|
-
HOSTS.with(|hosts| {
|
|
459
|
-
let mut hm = hosts.borrow_mut();
|
|
460
|
-
if let Some(host) = hm.get_mut(&root_id) {
|
|
461
|
-
host.commit_root(&tree);
|
|
462
|
-
}
|
|
463
|
-
});
|
|
464
555
|
}
|
|
465
556
|
});
|
|
557
|
+
let host = HOSTS.with(|hosts| hosts.borrow().get(&root_id).cloned());
|
|
558
|
+
if let Some(host) = host {
|
|
559
|
+
host.borrow_mut().commit_root(&tree);
|
|
560
|
+
}
|
|
466
561
|
IN_FLUSH.set(false);
|
|
562
|
+
flush_pending_layout_effects(root_id);
|
|
467
563
|
flush_pending_effects(root_id);
|
|
468
564
|
}
|
|
469
565
|
|
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
mod hooks;
|
|
4
4
|
|
|
5
|
-
use std::cell::RefCell;
|
|
6
5
|
use std::sync::Arc;
|
|
7
6
|
|
|
8
7
|
pub use hooks::{
|
|
9
|
-
alloc_root_id, current_root_id, drop_host_for_root, install_host_for_root,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
alloc_root_id, current_root_id, drop_host_for_root, install_host_for_root,
|
|
9
|
+
install_thread_local_host, native_create_root,
|
|
10
|
+
native_use_effect, native_use_layout_effect, native_use_memo, native_use_ref,
|
|
11
|
+
native_use_state, run_with_current_root,
|
|
12
|
+
schedule_flush,
|
|
13
|
+
unregister_root, unregister_root_hooks_and_effects, with_host_for_root,
|
|
14
|
+
with_thread_local_host, HookState, RootId, LEGACY_ROOT_ID,
|
|
13
15
|
};
|
|
14
16
|
|
|
15
17
|
use tishlang_core::{ObjectMap, Value, VmRef};
|
|
@@ -157,27 +159,6 @@ impl Host for HeadlessHost {
|
|
|
157
159
|
}
|
|
158
160
|
}
|
|
159
161
|
|
|
160
|
-
thread_local! {
|
|
161
|
-
static ACTIVE_HOST: RefCell<Option<Box<dyn Host>>> = RefCell::new(None);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/// Install the thread-local host used by [`schedule_flush`] / `createRoot`.
|
|
165
|
-
pub fn install_thread_local_host(host: Box<dyn Host>) {
|
|
166
|
-
ACTIVE_HOST.with(|c| {
|
|
167
|
-
*c.borrow_mut() = Some(host);
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
pub fn with_thread_local_host<R>(f: impl FnOnce(&mut dyn Host) -> R) -> Option<R> {
|
|
172
|
-
ACTIVE_HOST.with(|c| {
|
|
173
|
-
let mut opt = c.borrow_mut();
|
|
174
|
-
match opt.as_deref_mut() {
|
|
175
|
-
Some(host) => Some(f(host)),
|
|
176
|
-
None => None,
|
|
177
|
-
}
|
|
178
|
-
})
|
|
179
|
-
}
|
|
180
|
-
|
|
181
162
|
/// Tag registry hook for future host-specific intrinsic mapping (HTML tag → component kind).
|
|
182
163
|
#[derive(Default)]
|
|
183
164
|
pub struct TagRegistry;
|
package/crates/tish_vm/src/vm.rs
CHANGED
|
@@ -455,6 +455,46 @@ fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
|
|
|
455
455
|
"trunc".into(),
|
|
456
456
|
Value::native(|args: &[Value]| math_builtins::trunc(args)),
|
|
457
457
|
);
|
|
458
|
+
// Trig/hypot not covered by `math_builtins`; needed by the 3D engine's
|
|
459
|
+
// camera + character-controller math (atan2/hypot) on the wasm VM, where
|
|
460
|
+
// (unlike `--target js`) there is no host `Math` to fall through to.
|
|
461
|
+
math.insert(
|
|
462
|
+
"atan2".into(),
|
|
463
|
+
Value::native(|args: &[Value]| {
|
|
464
|
+
let y = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
|
|
465
|
+
let x = args.get(1).and_then(|v| v.as_number()).unwrap_or(f64::NAN);
|
|
466
|
+
Value::Number(y.atan2(x))
|
|
467
|
+
}),
|
|
468
|
+
);
|
|
469
|
+
math.insert(
|
|
470
|
+
"atan".into(),
|
|
471
|
+
Value::native(|args: &[Value]| {
|
|
472
|
+
let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
|
|
473
|
+
Value::Number(n.atan())
|
|
474
|
+
}),
|
|
475
|
+
);
|
|
476
|
+
math.insert(
|
|
477
|
+
"asin".into(),
|
|
478
|
+
Value::native(|args: &[Value]| {
|
|
479
|
+
let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
|
|
480
|
+
Value::Number(n.asin())
|
|
481
|
+
}),
|
|
482
|
+
);
|
|
483
|
+
math.insert(
|
|
484
|
+
"acos".into(),
|
|
485
|
+
Value::native(|args: &[Value]| {
|
|
486
|
+
let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
|
|
487
|
+
Value::Number(n.acos())
|
|
488
|
+
}),
|
|
489
|
+
);
|
|
490
|
+
math.insert(
|
|
491
|
+
"hypot".into(),
|
|
492
|
+
Value::native(|args: &[Value]| {
|
|
493
|
+
let nums: Vec<f64> = args.iter().filter_map(|v| v.as_number()).collect();
|
|
494
|
+
let sum_sq: f64 = nums.iter().map(|n| n * n).sum();
|
|
495
|
+
Value::Number(sum_sq.sqrt())
|
|
496
|
+
}),
|
|
497
|
+
);
|
|
458
498
|
math.insert("PI".into(), Value::Number(std::f64::consts::PI));
|
|
459
499
|
math.insert("E".into(), Value::Number(std::f64::consts::E));
|
|
460
500
|
g.insert("Math".into(), value_object_from_map(math));
|
|
@@ -231,6 +231,33 @@ pub fn compile_to_wasm(
|
|
|
231
231
|
emit_wasm_from_chunk(&chunk, output_path)
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
+
/// Compile a Tish project to a raw serialized bytecode chunk.
|
|
235
|
+
///
|
|
236
|
+
/// Writes a single `{output}` file of the exact bytes that the wasm/WASI runtime entry points
|
|
237
|
+
/// (`start` / `run`) deserialize directly — the same chunk `--target wasm` embeds as base64 in
|
|
238
|
+
/// its generated HTML loader, but written raw with no VM binary, JS glue, or HTML wrapper. Lets a
|
|
239
|
+
/// host that already ships the VM runtime (e.g. a bundler) consume the bytecode without the
|
|
240
|
+
/// throwaway standalone build.
|
|
241
|
+
pub fn compile_to_bytecode(
|
|
242
|
+
entry_path: &Path,
|
|
243
|
+
project_root: Option<&Path>,
|
|
244
|
+
output_path: &Path,
|
|
245
|
+
optimize: bool,
|
|
246
|
+
) -> Result<(), WasmError> {
|
|
247
|
+
let (chunk, _) = resolve_and_compile_to_chunk(entry_path, project_root, optimize)?;
|
|
248
|
+
let bytes = serialize(&chunk);
|
|
249
|
+
if let Some(parent) = output_path.parent().filter(|p| !p.as_os_str().is_empty()) {
|
|
250
|
+
std::fs::create_dir_all(parent).map_err(|e| WasmError {
|
|
251
|
+
message: format!("Cannot create output directory: {}", e),
|
|
252
|
+
})?;
|
|
253
|
+
}
|
|
254
|
+
std::fs::write(output_path, &bytes).map_err(|e| WasmError {
|
|
255
|
+
message: format!("Cannot write {}: {}", output_path.display(), e),
|
|
256
|
+
})?;
|
|
257
|
+
println!("Built: {} ({} bytes)", output_path.display(), bytes.len());
|
|
258
|
+
Ok(())
|
|
259
|
+
}
|
|
260
|
+
|
|
234
261
|
/// Compile a Tish project for Wasmtime/WASI.
|
|
235
262
|
///
|
|
236
263
|
/// Produces a single `{output}.wasm` with embedded bytecode. Run with:
|
|
@@ -12,6 +12,10 @@ crate-type = ["cdylib", "rlib"]
|
|
|
12
12
|
[features]
|
|
13
13
|
# For wasm32-unknown-unknown (browser): wasm-bindgen, console output
|
|
14
14
|
browser = ["dep:wasm-bindgen", "tishlang_vm/wasm"]
|
|
15
|
+
# Browser WebGPU / JS-interop FFI + requestAnimationFrame render loop (the
|
|
16
|
+
# `start(chunk, env)` entry). Reflection-based bridge over js-sys; no web-sys
|
|
17
|
+
# WebGPU bindings needed since the WebGPU command API is synchronous.
|
|
18
|
+
gpu = ["browser", "dep:js-sys"]
|
|
15
19
|
# Built-in modules for WASI (wasm32-wasip1): align with `tishlang_cranelift_runtime` / CLI caps
|
|
16
20
|
fs = ["tishlang_vm/fs"]
|
|
17
21
|
process = ["tishlang_vm/process"]
|
|
@@ -24,7 +28,9 @@ ws = ["tishlang_vm/ws"]
|
|
|
24
28
|
[dependencies]
|
|
25
29
|
tishlang_bytecode = { path = "../tish_bytecode", version = ">=0.1" }
|
|
26
30
|
tishlang_vm = { path = "../tish_vm", version = ">=0.1" }
|
|
31
|
+
tishlang_core = { path = "../tish_core", version = ">=0.1" }
|
|
27
32
|
wasm-bindgen = { version = "0.2", optional = true }
|
|
33
|
+
js-sys = { version = "0.3", optional = true }
|
|
28
34
|
|
|
29
35
|
# rand_core → getrandom 0.4 needs wasm_js on wasm32-unknown-unknown (browser VM build).
|
|
30
36
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|