@tishlang/tish 1.9.2 → 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/js_to_tish/src/transform/expr.rs +8 -6
- package/crates/js_to_tish/src/transform/stmt.rs +12 -13
- package/crates/tish/Cargo.toml +1 -1
- package/crates/tish/src/cargo_native_registry.rs +4 -1
- package/crates/tish/src/cli_help.rs +9 -1
- package/crates/tish/src/main.rs +66 -11
- package/crates/tish/tests/integration_test.rs +145 -7
- package/crates/tish_ast/src/ast.rs +3 -9
- package/crates/tish_build_utils/src/lib.rs +74 -23
- package/crates/tish_builtins/src/array.rs +2 -3
- package/crates/tish_builtins/src/construct.rs +15 -28
- package/crates/tish_builtins/src/globals.rs +18 -16
- package/crates/tish_builtins/src/helpers.rs +1 -4
- package/crates/tish_builtins/src/lib.rs +1 -0
- package/crates/tish_builtins/src/math.rs +7 -0
- package/crates/tish_builtins/src/object.rs +10 -10
- package/crates/tish_builtins/src/string.rs +27 -3
- package/crates/tish_builtins/src/symbol.rs +83 -0
- package/crates/tish_compile/src/codegen.rs +324 -158
- package/crates/tish_compile/src/lib.rs +39 -7
- package/crates/tish_compile/src/resolve.rs +191 -6
- package/crates/tish_compile/src/types.rs +6 -6
- package/crates/tish_compile_js/src/codegen.rs +8 -5
- package/crates/tish_core/src/console_style.rs +9 -0
- package/crates/tish_core/src/json.rs +17 -7
- package/crates/tish_core/src/macros.rs +2 -2
- package/crates/tish_core/src/value.rs +213 -4
- package/crates/tish_cranelift/src/link.rs +1 -1
- package/crates/tish_cranelift_runtime/Cargo.toml +4 -0
- package/crates/tish_eval/src/eval.rs +135 -73
- package/crates/tish_eval/src/http.rs +18 -12
- package/crates/tish_eval/src/lib.rs +29 -0
- package/crates/tish_eval/src/regex.rs +1 -1
- package/crates/tish_eval/src/value.rs +89 -4
- package/crates/tish_eval/src/value_convert.rs +30 -8
- package/crates/tish_fmt/src/lib.rs +4 -1
- package/crates/tish_lexer/src/lib.rs +7 -2
- package/crates/tish_llvm/src/lib.rs +2 -2
- package/crates/tish_lsp/src/builtin_goto.rs +111 -10
- package/crates/tish_lsp/src/import_goto.rs +35 -22
- package/crates/tish_lsp/src/main.rs +118 -85
- package/crates/tish_native/src/build.rs +270 -24
- package/crates/tish_native/src/config.rs +48 -0
- package/crates/tish_native/src/lib.rs +139 -12
- package/crates/tish_parser/src/lib.rs +5 -2
- package/crates/tish_parser/src/parser.rs +45 -75
- package/crates/tish_pg/src/error.rs +1 -1
- package/crates/tish_pg/src/lib.rs +61 -73
- package/crates/tish_resolve/src/lib.rs +283 -158
- package/crates/tish_resolve/src/pos.rs +10 -2
- package/crates/tish_runtime/Cargo.toml +3 -0
- package/crates/tish_runtime/src/http.rs +39 -39
- package/crates/tish_runtime/src/http_fetch.rs +12 -12
- package/crates/tish_runtime/src/lib.rs +35 -44
- package/crates/tish_runtime/src/native_promise.rs +0 -11
- package/crates/tish_runtime/src/promise.rs +14 -1
- package/crates/tish_runtime/src/promise_io.rs +1 -4
- package/crates/tish_runtime/src/timers.rs +12 -7
- package/crates/tish_runtime/src/ws.rs +40 -27
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +10 -8
- package/crates/tish_ui/src/jsx.rs +6 -4
- package/crates/tish_ui/src/lib.rs +5 -4
- package/crates/tish_ui/src/runtime/hooks.rs +123 -37
- package/crates/tish_ui/src/runtime/mod.rs +21 -41
- package/crates/tish_vm/Cargo.toml +2 -0
- package/crates/tish_vm/src/vm.rs +258 -153
- package/crates/tish_wasm/src/lib.rs +60 -7
- package/crates/tish_wasm_runtime/Cargo.toml +10 -1
- package/crates/tish_wasm_runtime/src/gpu.rs +413 -0
- package/crates/tish_wasm_runtime/src/lib.rs +7 -1
- package/crates/tishlang_cargo_bindgen/src/classify.rs +1 -3
- package/crates/tishlang_cargo_bindgen/src/discover.rs +10 -5
- package/crates/tishlang_cargo_bindgen/src/infer.rs +18 -8
- package/crates/tishlang_cargo_bindgen/src/lib.rs +25 -26
- package/crates/tishlang_cargo_bindgen/src/main.rs +41 -38
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +4 -1
- package/justfile +3 -3
- 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,10 +5,8 @@
|
|
|
5
5
|
//! - **Connection**: `send(data)`, `close()`, `readyState` (1=OPEN), `receive()` / `receiveTimeout(ms)`
|
|
6
6
|
//! - **Broadcast** (Node pattern): `server.clients.forEach(ws => ws.send(data))` or iterate room conns and `wsSend(ws, data)` (same as `ws.send(data)`)
|
|
7
7
|
|
|
8
|
-
use std::cell::RefCell;
|
|
9
8
|
use tishlang_core::VmRef;
|
|
10
9
|
use std::collections::HashMap;
|
|
11
|
-
use std::rc::Rc;
|
|
12
10
|
use std::sync::atomic::{AtomicU32, Ordering};
|
|
13
11
|
use std::sync::mpsc;
|
|
14
12
|
use std::sync::{Arc, Mutex};
|
|
@@ -151,7 +149,7 @@ fn conn_id_from_value(v: &Value) -> Option<u32> {
|
|
|
151
149
|
Value::Object(o) => {
|
|
152
150
|
let b = o.borrow();
|
|
153
151
|
// Direct conn: { _id, send, ... }
|
|
154
|
-
if let Some(idv) = b.get(
|
|
152
|
+
if let Some(idv) = b.strings.get("_id") {
|
|
155
153
|
if let Value::Number(n) = idv {
|
|
156
154
|
if n.is_finite() && *n >= 0.0 {
|
|
157
155
|
return Some(*n as u32);
|
|
@@ -159,7 +157,7 @@ fn conn_id_from_value(v: &Value) -> Option<u32> {
|
|
|
159
157
|
}
|
|
160
158
|
}
|
|
161
159
|
// Wrapper: { ws: conn, ... }
|
|
162
|
-
if let Some(ws) = b.get(
|
|
160
|
+
if let Some(ws) = b.strings.get("ws") {
|
|
163
161
|
return conn_id_from_value(ws);
|
|
164
162
|
}
|
|
165
163
|
None
|
|
@@ -221,7 +219,7 @@ fn conn_object(id: u32) -> Value {
|
|
|
221
219
|
Some(s) => {
|
|
222
220
|
let mut ev: ObjectMap = ObjectMap::default();
|
|
223
221
|
ev.insert(Arc::from("data"), Value::String(s.into()));
|
|
224
|
-
Value::
|
|
222
|
+
Value::object(ev)
|
|
225
223
|
}
|
|
226
224
|
None => Value::Null,
|
|
227
225
|
}),
|
|
@@ -243,18 +241,18 @@ fn conn_object(id: u32) -> Value {
|
|
|
243
241
|
Some(s) => {
|
|
244
242
|
let mut ev: ObjectMap = ObjectMap::default();
|
|
245
243
|
ev.insert(Arc::from("data"), Value::String(s.into()));
|
|
246
|
-
Value::
|
|
244
|
+
Value::object(ev)
|
|
247
245
|
}
|
|
248
246
|
None => Value::Null,
|
|
249
247
|
}
|
|
250
248
|
}),
|
|
251
249
|
);
|
|
252
|
-
Value::
|
|
250
|
+
Value::object(obj)
|
|
253
251
|
}
|
|
254
252
|
|
|
255
253
|
fn parse_port(args: &[Value]) -> Option<u16> {
|
|
256
254
|
args.first().and_then(|v| match v {
|
|
257
|
-
Value::Object(o) => o.borrow().get(
|
|
255
|
+
Value::Object(o) => o.borrow().strings.get("port").and_then(|v| match v {
|
|
258
256
|
Value::Number(n) if n.is_finite() && *n >= 0.0 => Some(*n as u16),
|
|
259
257
|
_ => None,
|
|
260
258
|
}),
|
|
@@ -485,7 +483,9 @@ pub fn web_socket_server_construct(args: &[Value]) -> Value {
|
|
|
485
483
|
.unwrap_or_default();
|
|
486
484
|
let cb = args.get(2).cloned().unwrap_or(Value::Null);
|
|
487
485
|
if event == "connection" {
|
|
488
|
-
so.borrow_mut()
|
|
486
|
+
so.borrow_mut()
|
|
487
|
+
.strings
|
|
488
|
+
.insert(Arc::from("_onConnection"), cb);
|
|
489
489
|
}
|
|
490
490
|
Value::Null
|
|
491
491
|
});
|
|
@@ -498,14 +498,20 @@ pub fn web_socket_server_construct(args: &[Value]) -> Value {
|
|
|
498
498
|
loop {
|
|
499
499
|
let handle_n = {
|
|
500
500
|
let b = so.borrow();
|
|
501
|
-
match b
|
|
501
|
+
match b
|
|
502
|
+
.strings
|
|
503
|
+
.get("_handle")
|
|
504
|
+
.cloned()
|
|
505
|
+
.unwrap_or(Value::Null)
|
|
506
|
+
{
|
|
502
507
|
Value::Number(n) if n.is_finite() && n >= 0.0 => n,
|
|
503
508
|
_ => break,
|
|
504
509
|
}
|
|
505
510
|
};
|
|
506
511
|
let cb = so
|
|
507
512
|
.borrow()
|
|
508
|
-
.
|
|
513
|
+
.strings
|
|
514
|
+
.get("_onConnection")
|
|
509
515
|
.cloned()
|
|
510
516
|
.unwrap_or(Value::Null);
|
|
511
517
|
let ws = web_socket_server_accept(&[Value::Number(handle_n)]);
|
|
@@ -527,7 +533,8 @@ pub fn web_socket_server_construct(args: &[Value]) -> Value {
|
|
|
527
533
|
};
|
|
528
534
|
let handle_n = so
|
|
529
535
|
.borrow()
|
|
530
|
-
.
|
|
536
|
+
.strings
|
|
537
|
+
.get("_handle")
|
|
531
538
|
.cloned()
|
|
532
539
|
.unwrap_or(Value::Null);
|
|
533
540
|
let timeout_ms = args.get(1).cloned().unwrap_or(Value::Number(100.0));
|
|
@@ -545,7 +552,7 @@ pub fn web_socket_server_construct(args: &[Value]) -> Value {
|
|
|
545
552
|
m.insert(Arc::from("on"), on_fn);
|
|
546
553
|
m.insert(Arc::from("listen"), listen_fn);
|
|
547
554
|
m.insert(Arc::from("acceptTimeout"), accept_timeout_fn);
|
|
548
|
-
Value::
|
|
555
|
+
Value::object(m)
|
|
549
556
|
}
|
|
550
557
|
|
|
551
558
|
#[cfg(test)]
|
|
@@ -560,7 +567,7 @@ mod tests {
|
|
|
560
567
|
let opts = {
|
|
561
568
|
let mut m: ObjectMap = ObjectMap::default();
|
|
562
569
|
m.insert(Arc::from("port"), Value::Number(port as f64));
|
|
563
|
-
Value::
|
|
570
|
+
Value::object(m)
|
|
564
571
|
};
|
|
565
572
|
|
|
566
573
|
let handle = match web_socket_server_listen(std::slice::from_ref(&opts)) {
|
|
@@ -575,20 +582,21 @@ mod tests {
|
|
|
575
582
|
};
|
|
576
583
|
// Echo one message
|
|
577
584
|
for _ in 0..50 {
|
|
578
|
-
let recv_fn = wso.borrow().get(
|
|
585
|
+
let recv_fn = wso.borrow().strings.get("receive").cloned();
|
|
579
586
|
if let Some(Value::Function(rf)) = recv_fn {
|
|
580
587
|
let msg = rf(&[]);
|
|
581
588
|
if !matches!(msg, Value::Null) {
|
|
582
589
|
let data = match msg {
|
|
583
590
|
Value::Object(ev) => ev
|
|
584
591
|
.borrow()
|
|
585
|
-
.
|
|
592
|
+
.strings
|
|
593
|
+
.get("data")
|
|
586
594
|
.map(|v| v.to_display_string())
|
|
587
595
|
.unwrap_or_default(),
|
|
588
596
|
_ => String::new(),
|
|
589
597
|
};
|
|
590
598
|
if let Some(Value::Function(sf)) =
|
|
591
|
-
wso.borrow().get(
|
|
599
|
+
wso.borrow().strings.get("send").cloned()
|
|
592
600
|
{
|
|
593
601
|
let _ = sf(&[Value::String(data.into())]);
|
|
594
602
|
}
|
|
@@ -607,13 +615,13 @@ mod tests {
|
|
|
607
615
|
let Value::Object(co) = client else {
|
|
608
616
|
panic!("client not object");
|
|
609
617
|
};
|
|
610
|
-
let send = co.borrow().get(
|
|
618
|
+
let send = co.borrow().strings.get("send").cloned().unwrap();
|
|
611
619
|
let Value::Function(send_f) = send else {
|
|
612
620
|
panic!("no send");
|
|
613
621
|
};
|
|
614
622
|
let _ = send_f(&[Value::String("hello".into())]);
|
|
615
623
|
|
|
616
|
-
let recv = co.borrow().get(
|
|
624
|
+
let recv = co.borrow().strings.get("receive").cloned().unwrap();
|
|
617
625
|
let Value::Function(recv_f) = recv else {
|
|
618
626
|
panic!("no receive");
|
|
619
627
|
};
|
|
@@ -630,7 +638,8 @@ mod tests {
|
|
|
630
638
|
};
|
|
631
639
|
let data = ev
|
|
632
640
|
.borrow()
|
|
633
|
-
.
|
|
641
|
+
.strings
|
|
642
|
+
.get("data")
|
|
634
643
|
.map(|v| v.to_display_string())
|
|
635
644
|
.unwrap_or_default();
|
|
636
645
|
assert_eq!(data, "hello");
|
|
@@ -645,7 +654,7 @@ mod tests {
|
|
|
645
654
|
let opts = {
|
|
646
655
|
let mut m: ObjectMap = ObjectMap::default();
|
|
647
656
|
m.insert(Arc::from("port"), Value::Number(port as f64));
|
|
648
|
-
Value::
|
|
657
|
+
Value::object(m)
|
|
649
658
|
};
|
|
650
659
|
|
|
651
660
|
let handle = match web_socket_server_listen(std::slice::from_ref(&opts)) {
|
|
@@ -658,7 +667,7 @@ mod tests {
|
|
|
658
667
|
let Value::Object(wso) = ws else {
|
|
659
668
|
panic!("accept failed");
|
|
660
669
|
};
|
|
661
|
-
let recv_fn = wso.borrow().get(
|
|
670
|
+
let recv_fn = wso.borrow().strings.get("receive").cloned();
|
|
662
671
|
let Value::Function(rf) = recv_fn.unwrap() else {
|
|
663
672
|
panic!("no receive");
|
|
664
673
|
};
|
|
@@ -669,7 +678,8 @@ mod tests {
|
|
|
669
678
|
let data = match &msg {
|
|
670
679
|
Value::Object(ev) => ev
|
|
671
680
|
.borrow()
|
|
672
|
-
.
|
|
681
|
+
.strings
|
|
682
|
+
.get("data")
|
|
673
683
|
.map(|v| v.to_display_string())
|
|
674
684
|
.unwrap_or_default(),
|
|
675
685
|
_ => String::new(),
|
|
@@ -695,7 +705,7 @@ mod tests {
|
|
|
695
705
|
let Value::Object(co) = client else {
|
|
696
706
|
panic!("client not object");
|
|
697
707
|
};
|
|
698
|
-
let send = co.borrow().get(
|
|
708
|
+
let send = co.borrow().strings.get("send").cloned().unwrap();
|
|
699
709
|
let Value::Function(send_f) = send else {
|
|
700
710
|
panic!("no send");
|
|
701
711
|
};
|
|
@@ -705,7 +715,8 @@ mod tests {
|
|
|
705
715
|
// Client uses receiveTimeout like the agent
|
|
706
716
|
let recv_timeout = co
|
|
707
717
|
.borrow()
|
|
708
|
-
.
|
|
718
|
+
.strings
|
|
719
|
+
.get("receiveTimeout")
|
|
709
720
|
.cloned()
|
|
710
721
|
.unwrap();
|
|
711
722
|
let Value::Function(recv_timeout_f) = recv_timeout else {
|
|
@@ -719,7 +730,8 @@ mod tests {
|
|
|
719
730
|
};
|
|
720
731
|
let data1 = ev1
|
|
721
732
|
.borrow()
|
|
722
|
-
.
|
|
733
|
+
.strings
|
|
734
|
+
.get("data")
|
|
723
735
|
.map(|v| v.to_display_string())
|
|
724
736
|
.unwrap_or_default();
|
|
725
737
|
assert!(
|
|
@@ -734,7 +746,8 @@ mod tests {
|
|
|
734
746
|
};
|
|
735
747
|
let data2 = ev2
|
|
736
748
|
.borrow()
|
|
737
|
-
.
|
|
749
|
+
.strings
|
|
750
|
+
.get("data")
|
|
738
751
|
.map(|v| v.to_display_string())
|
|
739
752
|
.unwrap_or_default();
|
|
740
753
|
assert!(
|
|
@@ -52,11 +52,15 @@ fn fetch_readable_stream_read_chunks() {
|
|
|
52
52
|
_ => panic!("expected object response, got {:?}", resp),
|
|
53
53
|
};
|
|
54
54
|
assert!(obj
|
|
55
|
+
.strings
|
|
55
56
|
.get(&std::sync::Arc::from("ok"))
|
|
56
57
|
.map(|v| matches!(v, Value::Bool(true)))
|
|
57
58
|
.unwrap_or(false));
|
|
58
59
|
|
|
59
|
-
let body = obj
|
|
60
|
+
let body = obj
|
|
61
|
+
.strings
|
|
62
|
+
.get(&std::sync::Arc::from("body"))
|
|
63
|
+
.expect("body");
|
|
60
64
|
let stream = match body {
|
|
61
65
|
Value::Opaque(s) => s.as_ref(),
|
|
62
66
|
_ => panic!("expected ReadableStream opaque"),
|
|
@@ -74,14 +78,12 @@ fn fetch_readable_stream_read_chunks() {
|
|
|
74
78
|
let (done, chunk_bytes) = match chunk {
|
|
75
79
|
Value::Object(o) => {
|
|
76
80
|
let m = o.borrow();
|
|
77
|
-
let done = m
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
_ => None,
|
|
82
|
-
})
|
|
83
|
-
.unwrap_or(true);
|
|
81
|
+
let done = match m.strings.get(&std::sync::Arc::from("done")) {
|
|
82
|
+
Some(Value::Bool(b)) => *b,
|
|
83
|
+
_ => true,
|
|
84
|
+
};
|
|
84
85
|
let bytes = m
|
|
86
|
+
.strings
|
|
85
87
|
.get(&std::sync::Arc::from("value"))
|
|
86
88
|
.map(|v| byte_array_to_vec(v))
|
|
87
89
|
.unwrap_or_default();
|
|
@@ -344,7 +344,9 @@ fn collect_fun_decl_names_expr(expr: &Expr, names: &mut HashSet<String>) {
|
|
|
344
344
|
| Expr::PrefixInc { .. }
|
|
345
345
|
| Expr::PostfixDec { .. }
|
|
346
346
|
| Expr::PrefixDec { .. } => {}
|
|
347
|
-
Expr::JsxElement {
|
|
347
|
+
Expr::JsxElement {
|
|
348
|
+
props, children, ..
|
|
349
|
+
} => {
|
|
348
350
|
for p in props {
|
|
349
351
|
match p {
|
|
350
352
|
JsxProp::Attr { value, .. } => {
|
|
@@ -466,14 +468,14 @@ where
|
|
|
466
468
|
JsxProp::Spread(e) => {
|
|
467
469
|
let val = emit_expr(e)?;
|
|
468
470
|
parts.push(format!(
|
|
469
|
-
"if let Value::Object(ref _spread) = {} {{ for (k, v) in _spread.borrow().iter() {{ _obj.insert(Arc::clone(k), v.clone()); }} }}",
|
|
471
|
+
"if let Value::Object(ref _spread) = {} {{ for (k, v) in _spread.borrow().strings.iter() {{ _obj.insert(Arc::clone(k), v.clone()); }} }}",
|
|
470
472
|
val
|
|
471
473
|
));
|
|
472
474
|
}
|
|
473
475
|
}
|
|
474
476
|
}
|
|
475
477
|
Ok(format!(
|
|
476
|
-
"{{ let mut _obj: ObjectMap = ObjectMap::default(); {} Value::
|
|
478
|
+
"{{ let mut _obj: ObjectMap = ObjectMap::default(); {} Value::object(_obj) }}",
|
|
477
479
|
parts.join(" ")
|
|
478
480
|
))
|
|
479
481
|
} else {
|
|
@@ -495,7 +497,7 @@ where
|
|
|
495
497
|
}
|
|
496
498
|
}
|
|
497
499
|
Ok(format!(
|
|
498
|
-
"Value::
|
|
500
|
+
"Value::object(ObjectMap::from([{}]))",
|
|
499
501
|
kv.join(", ")
|
|
500
502
|
))
|
|
501
503
|
}
|
|
@@ -12,8 +12,9 @@ pub mod runtime;
|
|
|
12
12
|
#[cfg(feature = "runtime")]
|
|
13
13
|
pub use runtime::{
|
|
14
14
|
alloc_root_id, current_root_id, drop_host_for_root, fragment_value, install_host_for_root,
|
|
15
|
-
install_thread_local_host, native_create_root,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
install_thread_local_host, native_create_root,
|
|
16
|
+
native_use_effect, native_use_layout_effect, native_use_memo, native_use_ref,
|
|
17
|
+
native_use_state, run_with_current_root, ui_h, ui_text,
|
|
18
|
+
unregister_root, unregister_root_hooks_and_effects, with_host_for_root,
|
|
19
|
+
with_thread_local_host, HeadlessHost, Host, RootId, FRAGMENT_SENTINEL, LEGACY_ROOT_ID,
|
|
19
20
|
};
|
|
@@ -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();
|
|
@@ -189,20 +211,15 @@ fn memo_dep_eq(a: &Value, b: &Value) -> bool {
|
|
|
189
211
|
if ab.len() != bb.len() {
|
|
190
212
|
return false;
|
|
191
213
|
}
|
|
192
|
-
ab.iter()
|
|
193
|
-
.zip(bb.iter())
|
|
194
|
-
.all(|(x, y)| memo_dep_eq(x, y))
|
|
214
|
+
ab.iter().zip(bb.iter()).all(|(x, y)| memo_dep_eq(x, y))
|
|
195
215
|
}
|
|
216
|
+
(Value::Object(a), Value::Object(b)) => tishlang_core::VmRef::ptr_eq(a, b),
|
|
196
217
|
_ => false,
|
|
197
218
|
}
|
|
198
219
|
}
|
|
199
220
|
|
|
200
221
|
fn memo_deps_unchanged(prev: &[Value], next: &[Value]) -> bool {
|
|
201
|
-
prev.len() == next.len()
|
|
202
|
-
&& prev
|
|
203
|
-
.iter()
|
|
204
|
-
.zip(next.iter())
|
|
205
|
-
.all(|(a, b)| memo_dep_eq(a, b))
|
|
222
|
+
prev.len() == next.len() && prev.iter().zip(next.iter()).all(|(a, b)| memo_dep_eq(a, b))
|
|
206
223
|
}
|
|
207
224
|
|
|
208
225
|
fn root_id_for_hooks() -> RootId {
|
|
@@ -245,7 +262,7 @@ pub fn native_use_state(args: &[Value]) -> Value {
|
|
|
245
262
|
st.flush_scheduled = true;
|
|
246
263
|
}
|
|
247
264
|
});
|
|
248
|
-
if !IN_FLUSH.get() {
|
|
265
|
+
if !IN_FLUSH.get() && !IN_EFFECT_FLUSH.get() {
|
|
249
266
|
drain_flush_queue();
|
|
250
267
|
}
|
|
251
268
|
Value::Null
|
|
@@ -290,9 +307,29 @@ pub fn native_use_memo(args: &[Value]) -> Value {
|
|
|
290
307
|
})
|
|
291
308
|
}
|
|
292
309
|
|
|
293
|
-
/// `
|
|
294
|
-
|
|
295
|
-
|
|
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 {
|
|
296
333
|
let Some(Value::Function(effect_fn)) = args.first() else {
|
|
297
334
|
return Value::Null;
|
|
298
335
|
};
|
|
@@ -307,9 +344,22 @@ pub fn native_use_effect(args: &[Value]) -> Value {
|
|
|
307
344
|
HOOKS.with(|h| {
|
|
308
345
|
let mut map = h.borrow_mut();
|
|
309
346
|
let st = map.entry(root_id).or_default();
|
|
310
|
-
let
|
|
311
|
-
|
|
312
|
-
|
|
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();
|
|
313
363
|
let mut cells_b = cells.borrow_mut();
|
|
314
364
|
while cells_b.len() <= i {
|
|
315
365
|
cells_b.push(EffectCell::default());
|
|
@@ -321,7 +371,7 @@ pub fn native_use_effect(args: &[Value]) -> Value {
|
|
|
321
371
|
drop(cells_b);
|
|
322
372
|
|
|
323
373
|
if should_run {
|
|
324
|
-
|
|
374
|
+
pending.borrow_mut().push(PendingEffect {
|
|
325
375
|
slot: i,
|
|
326
376
|
effect_fn: Value::Function(effect_fn),
|
|
327
377
|
new_deps: deps,
|
|
@@ -331,18 +381,29 @@ pub fn native_use_effect(args: &[Value]) -> Value {
|
|
|
331
381
|
})
|
|
332
382
|
}
|
|
333
383
|
|
|
334
|
-
|
|
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
|
+
) {
|
|
335
400
|
let pending: Vec<PendingEffect> = HOOKS.with(|h| {
|
|
336
401
|
h.borrow_mut()
|
|
337
402
|
.get_mut(&root_id)
|
|
338
|
-
.map(|st| std::mem::take(&mut *st.
|
|
403
|
+
.map(|st| std::mem::take(&mut *pending_key(st).borrow_mut()))
|
|
339
404
|
.unwrap_or_default()
|
|
340
405
|
});
|
|
341
|
-
let cells_rc = HOOKS.with(|h|
|
|
342
|
-
h.borrow()
|
|
343
|
-
.get(&root_id)
|
|
344
|
-
.map(|st| st.effect_cells.clone())
|
|
345
|
-
});
|
|
406
|
+
let cells_rc = HOOKS.with(|h| h.borrow().get(&root_id).map(cells_key));
|
|
346
407
|
let Some(cells_rc) = cells_rc else {
|
|
347
408
|
return;
|
|
348
409
|
};
|
|
@@ -380,6 +441,26 @@ fn flush_pending_effects(root_id: RootId) {
|
|
|
380
441
|
}
|
|
381
442
|
}
|
|
382
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
|
+
|
|
383
464
|
fn parse_root_id_arg(args: &[Value]) -> RootId {
|
|
384
465
|
match args.first() {
|
|
385
466
|
Some(Value::Number(n)) if n.is_finite() && *n >= 1.0 && n.fract() == 0.0 => *n as u64,
|
|
@@ -405,10 +486,10 @@ pub fn native_create_root(args: &[Value]) -> Value {
|
|
|
405
486
|
drain_flush_queue();
|
|
406
487
|
Value::Null
|
|
407
488
|
});
|
|
408
|
-
Value::
|
|
489
|
+
Value::object(ObjectMap::from([(
|
|
409
490
|
std::sync::Arc::from("render"),
|
|
410
491
|
render_fn,
|
|
411
|
-
)]))
|
|
492
|
+
)]))
|
|
412
493
|
}
|
|
413
494
|
|
|
414
495
|
/// Request a re-render (coalesced; safe if called during flush).
|
|
@@ -426,6 +507,10 @@ pub fn schedule_flush() {
|
|
|
426
507
|
}
|
|
427
508
|
|
|
428
509
|
fn drain_flush_queue() {
|
|
510
|
+
drain_flush_queue_on_current_thread();
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
fn drain_flush_queue_on_current_thread() {
|
|
429
514
|
loop {
|
|
430
515
|
let root_id = HOOKS.with(|h| {
|
|
431
516
|
h.borrow()
|
|
@@ -450,8 +535,11 @@ fn drain_flush_queue() {
|
|
|
450
535
|
let st = map.get_mut(&root_id)?;
|
|
451
536
|
st.cursor = 0;
|
|
452
537
|
st.memo_cursor = 0;
|
|
538
|
+
st.ref_cursor = 0;
|
|
453
539
|
st.effect_cursor = 0;
|
|
540
|
+
st.layout_effect_cursor = 0;
|
|
454
541
|
st.pending_effects.borrow_mut().clear();
|
|
542
|
+
st.pending_layout_effects.borrow_mut().clear();
|
|
455
543
|
let app = st.root_app.clone()?;
|
|
456
544
|
let Value::Function(f) = app else {
|
|
457
545
|
return None;
|
|
@@ -462,18 +550,16 @@ fn drain_flush_queue() {
|
|
|
462
550
|
if let Some(f) = app_fn {
|
|
463
551
|
let tree = f(&[]);
|
|
464
552
|
HOOKS.with(|h| {
|
|
465
|
-
let
|
|
466
|
-
if let Some(st) = map.get_mut(&root_id) {
|
|
553
|
+
if let Some(st) = h.borrow_mut().get_mut(&root_id) {
|
|
467
554
|
st.root_vnode = Some(tree.clone());
|
|
468
|
-
HOSTS.with(|hosts| {
|
|
469
|
-
let mut hm = hosts.borrow_mut();
|
|
470
|
-
if let Some(host) = hm.get_mut(&root_id) {
|
|
471
|
-
host.commit_root(&tree);
|
|
472
|
-
}
|
|
473
|
-
});
|
|
474
555
|
}
|
|
475
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
|
+
}
|
|
476
561
|
IN_FLUSH.set(false);
|
|
562
|
+
flush_pending_layout_effects(root_id);
|
|
477
563
|
flush_pending_effects(root_id);
|
|
478
564
|
}
|
|
479
565
|
|