1use crate::debugger::VmDebuggerHandle;
9use intuicio_core::{
10 context::Context,
11 function::FunctionBody,
12 registry::{Registry, RegistryHandle},
13 script::{ScriptExpression, ScriptFunctionGenerator, ScriptHandle, ScriptOperation},
14};
15use intuicio_data::managed::{ManagedLazy, ManagedRefMut};
16use typid::ID;
17
18pub type VmScopeSymbol = ID<()>;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum VmScopeResult {
28 Continue,
30 Completed,
32 Suspended,
34}
35
36impl VmScopeResult {
37 pub fn can_continue(self) -> bool {
39 self == VmScopeResult::Continue
40 }
41
42 pub fn is_completed(self) -> bool {
44 self == VmScopeResult::Completed
45 }
46
47 pub fn is_suspended(self) -> bool {
49 self == VmScopeResult::Suspended
50 }
51
52 pub fn can_progress(self) -> bool {
55 !self.is_completed()
56 }
57}
58
59pub struct VmScope<'a, SE: ScriptExpression> {
69 handle: ScriptHandle<'a, SE>,
70 symbol: VmScopeSymbol,
71 position: usize,
72 child: Option<Box<Self>>,
73 debugger: Option<VmDebuggerHandle<SE>>,
74}
75
76impl<'a, SE: ScriptExpression> VmScope<'a, SE> {
77 pub fn new(handle: ScriptHandle<'a, SE>, symbol: VmScopeSymbol) -> Self {
81 Self {
82 handle,
83 symbol,
84 position: 0,
85 child: None,
86 debugger: None,
87 }
88 }
89
90 pub unsafe fn restore(mut self, position: usize, child: Option<Self>) -> Self {
103 self.position = position;
104 self.child = child.map(Box::new);
105 self
106 }
107
108 pub fn with_debugger(mut self, debugger: Option<VmDebuggerHandle<SE>>) -> Self {
110 self.debugger = debugger;
111 self
112 }
113
114 #[allow(clippy::type_complexity)]
119 pub fn into_inner(
120 self,
121 ) -> (
122 ScriptHandle<'a, SE>,
123 VmScopeSymbol,
124 usize,
125 Option<Box<Self>>,
126 Option<VmDebuggerHandle<SE>>,
127 ) {
128 (
129 self.handle,
130 self.symbol,
131 self.position,
132 self.child,
133 self.debugger,
134 )
135 }
136
137 pub fn symbol(&self) -> VmScopeSymbol {
139 self.symbol
140 }
141
142 pub fn position(&self) -> usize {
144 self.position
145 }
146
147 pub fn has_completed(&self) -> bool {
151 self.position >= self.handle.len()
152 }
153
154 pub fn child(&self) -> Option<&Self> {
156 self.child.as_deref()
157 }
158
159 pub fn run(&mut self, context: &mut Context, registry: &Registry) {
168 while self.step(context, registry).can_progress() {}
169 }
170
171 pub fn run_until_suspended(
180 &mut self,
181 context: &mut Context,
182 registry: &Registry,
183 ) -> VmScopeResult {
184 loop {
185 match self.step(context, registry) {
186 VmScopeResult::Continue => {}
187 result => return result,
188 }
189 }
190 }
191
192 pub fn step(&mut self, context: &mut Context, registry: &Registry) -> VmScopeResult {
205 if let Some(child) = &mut self.child {
206 match child.step(context, registry) {
207 VmScopeResult::Completed => {
208 self.child = None;
209 }
210 result => return result,
211 }
212 }
213 if self.position == 0
214 && let Some(debugger) = self.debugger.as_ref()
215 && let Ok(mut debugger) = debugger.try_write()
216 {
217 debugger.on_enter_scope(self, context, registry);
218 }
219 let result = if let Some(operation) = self.handle.get(self.position) {
220 if let Some(debugger) = self.debugger.as_ref()
221 && let Ok(mut debugger) = debugger.try_write()
222 {
223 debugger.on_enter_operation(self, operation, self.position, context, registry);
224 }
225 let position = self.position;
226 let result = match operation {
227 ScriptOperation::None => {
228 self.position += 1;
229 VmScopeResult::Continue
230 }
231 ScriptOperation::Expression { expression } => {
232 expression.evaluate(context, registry);
233 self.position += 1;
234 VmScopeResult::Continue
235 }
236 ScriptOperation::DefineRegister { query } => {
237 let handle = registry
238 .types()
239 .find(|handle| query.is_valid(handle))
240 .unwrap_or_else(|| {
241 panic!("Could not define register for non-existent type: {query:#?}")
242 });
243 unsafe {
244 context
245 .registers()
246 .push_register_raw(handle.type_hash(), *handle.layout())
247 };
248 self.position += 1;
249 VmScopeResult::Continue
250 }
251 ScriptOperation::DropRegister { index } => {
252 let index = context.absolute_register_index(*index);
253 context
254 .registers()
255 .access_register(index)
256 .unwrap_or_else(|| {
257 panic!("Could not access non-existent register: {index}")
258 })
259 .free();
260 self.position += 1;
261 VmScopeResult::Continue
262 }
263 ScriptOperation::PushFromRegister { index } => {
264 let index = context.absolute_register_index(*index);
265 let (stack, registers) = context.stack_and_registers();
266 let mut register = registers.access_register(index).unwrap_or_else(|| {
267 panic!("Could not access non-existent register: {index}")
268 });
269 if !stack.push_from_register(&mut register) {
270 panic!("Could not push data from register: {index}");
271 }
272 self.position += 1;
273 VmScopeResult::Continue
274 }
275 ScriptOperation::PopToRegister { index } => {
276 let index = context.absolute_register_index(*index);
277 let (stack, registers) = context.stack_and_registers();
278 let mut register = registers.access_register(index).unwrap_or_else(|| {
279 panic!("Could not access non-existent register: {index}")
280 });
281 if !stack.pop_to_register(&mut register) {
282 panic!("Could not pop data to register: {index}");
283 }
284 self.position += 1;
285 VmScopeResult::Continue
286 }
287 ScriptOperation::MoveRegister { from, to } => {
288 let from = context.absolute_register_index(*from);
289 let to = context.absolute_register_index(*to);
290 let (mut source, mut target) = context
291 .registers()
292 .access_registers_pair(from, to)
293 .unwrap_or_else(|| {
294 panic!("Could not access non-existent registers pair: {from} and {to}")
295 });
296 source.move_to(&mut target);
297 self.position += 1;
298 VmScopeResult::Continue
299 }
300 ScriptOperation::CallFunction { query } => {
301 let handle = registry
302 .functions()
303 .find(|handle| query.is_valid(handle.signature()))
304 .unwrap_or_else(|| {
305 panic!("Could not call non-existent function: {query:#?}")
306 });
307 handle.invoke(context, registry);
308 self.position += 1;
309 VmScopeResult::Continue
310 }
311 ScriptOperation::BranchScope {
312 scope_success,
313 scope_failure,
314 } => {
315 if context.stack().pop::<bool>().unwrap() {
316 self.child = Some(Box::new(
317 Self::new(scope_success.clone(), self.symbol)
318 .with_debugger(self.debugger.clone()),
319 ));
320 } else if let Some(scope_failure) = scope_failure {
321 self.child = Some(Box::new(
322 Self::new(scope_failure.clone(), self.symbol)
323 .with_debugger(self.debugger.clone()),
324 ));
325 }
326 self.position += 1;
327 VmScopeResult::Continue
328 }
329 ScriptOperation::LoopScope { scope } => {
330 if !context.stack().pop::<bool>().unwrap() {
331 self.position += 1;
332 } else {
333 self.child = Some(Box::new(
334 Self::new(scope.clone(), self.symbol)
335 .with_debugger(self.debugger.clone()),
336 ));
337 }
338 VmScopeResult::Continue
339 }
340 ScriptOperation::PushScope { scope } => {
341 context.store_registers();
342 self.child = Some(Box::new(
343 Self::new(scope.clone(), self.symbol).with_debugger(self.debugger.clone()),
344 ));
345 self.position += 1;
346 VmScopeResult::Continue
347 }
348 ScriptOperation::PopScope => {
349 context.restore_registers();
350 self.position = self.handle.len();
351 VmScopeResult::Completed
352 }
353 ScriptOperation::ContinueScopeConditionally => {
354 if context.stack().pop::<bool>().unwrap() {
355 self.position += 1;
356 VmScopeResult::Continue
357 } else {
358 self.position = self.handle.len();
359 VmScopeResult::Completed
360 }
361 }
362 ScriptOperation::Suspend => {
363 self.position += 1;
364 VmScopeResult::Suspended
365 }
366 };
367 if let Some(debugger) = self.debugger.as_ref()
368 && let Ok(mut debugger) = debugger.try_write()
369 {
370 debugger.on_exit_operation(self, operation, position, context, registry);
371 }
372 result
373 } else {
374 VmScopeResult::Completed
375 };
376 if (!result.can_progress() || self.position >= self.handle.len())
377 && let Some(debugger) = self.debugger.as_ref()
378 && let Ok(mut debugger) = debugger.try_write()
379 {
380 debugger.on_exit_scope(self, context, registry);
381 }
382 result
383 }
384}
385
386impl<SE: ScriptExpression + 'static> ScriptFunctionGenerator<SE> for VmScope<'static, SE> {
387 type Input = Option<VmDebuggerHandle<SE>>;
388 type Output = VmScopeSymbol;
389
390 fn generate_function_body(
391 script: ScriptHandle<'static, SE>,
392 debugger: Self::Input,
393 ) -> Option<(FunctionBody, Self::Output)> {
394 let symbol = VmScopeSymbol::new();
395 Some((
396 FunctionBody::closure(move |context, registry| {
397 Self::new(script.clone(), symbol)
398 .with_debugger(debugger.clone())
399 .run(context, registry);
400 }),
401 symbol,
402 ))
403 }
404}
405
406impl<SE: ScriptExpression> Clone for VmScope<'_, SE> {
407 fn clone(&self) -> Self {
408 Self {
409 handle: self.handle.clone(),
410 symbol: self.symbol,
411 position: self.position,
412 child: self.child.as_ref().map(|child| Box::new((**child).clone())),
413 debugger: self.debugger.clone(),
414 }
415 }
416}
417
418pub enum VmScopeFutureContext {
420 Owned(Box<Context>),
422 RefMut(ManagedRefMut<Context>),
424 Lazy(ManagedLazy<Context>),
427}
428
429impl From<Box<Context>> for VmScopeFutureContext {
430 fn from(value: Box<Context>) -> Self {
431 Self::Owned(value)
432 }
433}
434
435impl From<Context> for VmScopeFutureContext {
436 fn from(value: Context) -> Self {
437 Self::Owned(Box::new(value))
438 }
439}
440
441impl From<ManagedRefMut<Context>> for VmScopeFutureContext {
442 fn from(value: ManagedRefMut<Context>) -> Self {
443 Self::RefMut(value)
444 }
445}
446
447impl From<ManagedLazy<Context>> for VmScopeFutureContext {
448 fn from(value: ManagedLazy<Context>) -> Self {
449 Self::Lazy(value)
450 }
451}
452
453pub struct VmScopeFuture<'a, SE: ScriptExpression> {
463 pub scope: VmScope<'a, SE>,
465 pub context: VmScopeFutureContext,
467 pub registry: RegistryHandle,
469 pub operations_per_poll: usize,
471}
472
473impl<'a, SE: ScriptExpression> VmScopeFuture<'a, SE> {
474 pub fn new(
476 scope: VmScope<'a, SE>,
477 context: impl Into<VmScopeFutureContext>,
478 registry: RegistryHandle,
479 ) -> Self {
480 Self {
481 scope,
482 context: context.into(),
483 registry,
484 operations_per_poll: usize::MAX,
485 }
486 }
487
488 pub fn operations_per_poll(mut self, value: usize) -> Self {
493 self.operations_per_poll = value;
494 self
495 }
496
497 fn step(&mut self) -> Option<VmScopeResult> {
498 match &mut self.context {
499 VmScopeFutureContext::Owned(context) => {
500 Some(self.scope.step(&mut *context, &self.registry))
501 }
502 VmScopeFutureContext::RefMut(context) => {
503 let mut context = context.write()?;
504 Some(self.scope.step(&mut context, &self.registry))
505 }
506 VmScopeFutureContext::Lazy(context) => {
507 let mut context = context.write()?;
508 Some(self.scope.step(&mut context, &self.registry))
509 }
510 }
511 }
512}
513
514impl<SE: ScriptExpression> Future for VmScopeFuture<'_, SE> {
515 type Output = ();
516
517 fn poll(
518 mut self: std::pin::Pin<&mut Self>,
519 _cx: &mut std::task::Context<'_>,
520 ) -> std::task::Poll<Self::Output> {
521 for _ in 0..self.operations_per_poll {
522 match self.step() {
523 None => return std::task::Poll::Pending,
524 Some(VmScopeResult::Completed) => return std::task::Poll::Ready(()),
525 Some(VmScopeResult::Suspended) => return std::task::Poll::Pending,
526 Some(VmScopeResult::Continue) => {}
527 }
528 }
529 std::task::Poll::Pending
530 }
531}
532
533#[cfg(test)]
534mod tests {
535 use crate::scope::*;
536 use intuicio_core::{
537 Visibility,
538 function::{Function, FunctionParameter, FunctionQuery, FunctionSignature},
539 script::{ScriptBuilder, ScriptFunction, ScriptFunctionParameter, ScriptFunctionSignature},
540 types::{TypeQuery, struct_type::NativeStructBuilder},
541 };
542 use intuicio_data::managed::Managed;
543
544 #[test]
545 fn test_async() {
546 fn is_async<T: Send + Sync>() {}
547
548 is_async::<VmScope<()>>();
549 is_async::<VmScopeFuture<()>>();
550 is_async::<VmScopeFutureContext>();
551 }
552
553 #[test]
554 fn test_vm_scope() {
555 let i32_handle = NativeStructBuilder::new::<i32>()
556 .build()
557 .into_type()
558 .into_handle();
559 let mut registry = Registry::default().with_basic_types();
560 registry.add_function(Function::new(
561 FunctionSignature::new("add")
562 .with_input(FunctionParameter::new("a", i32_handle.clone()))
563 .with_input(FunctionParameter::new("b", i32_handle.clone()))
564 .with_output(FunctionParameter::new("result", i32_handle.clone())),
565 FunctionBody::closure(|context, _| {
566 let a = context.stack().pop::<i32>().unwrap();
567 let b = context.stack().pop::<i32>().unwrap();
568 context.stack().push(a + b);
569 }),
570 ));
571 registry.add_function(
572 VmScope::<()>::generate_function(
573 &ScriptFunction {
574 signature: ScriptFunctionSignature {
575 meta: None,
576 name: "add_script".to_owned(),
577 module_name: None,
578 type_query: None,
579 visibility: Visibility::Public,
580 inputs: vec![
581 ScriptFunctionParameter {
582 meta: None,
583 name: "a".to_owned(),
584 type_query: TypeQuery::of::<i32>(),
585 },
586 ScriptFunctionParameter {
587 meta: None,
588 name: "b".to_owned(),
589 type_query: TypeQuery::of::<i32>(),
590 },
591 ],
592 outputs: vec![ScriptFunctionParameter {
593 meta: None,
594 name: "result".to_owned(),
595 type_query: TypeQuery::of::<i32>(),
596 }],
597 },
598 script: ScriptBuilder::<()>::default()
599 .define_register(TypeQuery::of::<i32>())
600 .pop_to_register(0)
601 .push_from_register(0)
602 .call_function(FunctionQuery {
603 name: Some("add".into()),
604 ..Default::default()
605 })
606 .build(),
607 },
608 ®istry,
609 None,
610 )
611 .unwrap()
612 .0,
613 );
614 registry.add_type_handle(i32_handle);
615 let mut context = Context::new(10240, 10240);
616 let (result,) = registry
617 .find_function(FunctionQuery {
618 name: Some("add".into()),
619 ..Default::default()
620 })
621 .unwrap()
622 .call::<(i32,), _>(&mut context, ®istry, (40, 2), true);
623 assert_eq!(result, 42);
624 assert_eq!(context.stack().position(), 0);
625 assert_eq!(context.registers().position(), 0);
626 let (result,) = registry
627 .find_function(FunctionQuery {
628 name: Some("add_script".into()),
629 ..Default::default()
630 })
631 .unwrap()
632 .call::<(i32,), _>(&mut context, ®istry, (40, 2), true);
633 assert_eq!(result, 42);
634 assert_eq!(context.stack().position(), 0);
635 assert_eq!(context.registers().position(), 0);
636 }
637
638 #[test]
639 fn test_vm_scope_future() {
640 enum Expression {
641 Literal(i32),
642 Increment,
643 }
644
645 impl ScriptExpression for Expression {
646 fn evaluate(&self, context: &mut Context, _registry: &Registry) {
647 match self {
648 Expression::Literal(value) => {
649 context.stack().push(*value);
650 }
651 Expression::Increment => {
652 let value = context.stack().pop::<i32>().unwrap();
653 context.stack().push(value + 1);
654 }
655 }
656 }
657 }
658
659 let mut context = Managed::new(Context::new(10240, 10240));
660 let registry = RegistryHandle::default();
661
662 let script = ScriptBuilder::<Expression>::default()
663 .expression(Expression::Literal(42))
664 .suspend()
665 .expression(Expression::Increment)
666 .build();
667 let scope = VmScope::new(script, VmScopeSymbol::new());
668 let mut future = VmScopeFuture::new(scope, context.lazy(), registry);
669 let mut future = std::pin::Pin::new(&mut future);
670 let mut cx = std::task::Context::from_waker(std::task::Waker::noop());
671 assert_eq!(context.write().unwrap().stack().position(), 0);
672
673 assert_eq!(future.as_mut().poll(&mut cx), std::task::Poll::Pending);
674 assert_eq!(
675 context.write().unwrap().stack().position(),
676 if cfg!(feature = "typehash_debug_name") {
677 28
678 } else {
679 12
680 }
681 );
682 assert_eq!(context.write().unwrap().stack().pop::<i32>().unwrap(), 42);
683 context.write().unwrap().stack().push(1);
684
685 assert_eq!(future.as_mut().poll(&mut cx), std::task::Poll::Ready(()));
686 assert_eq!(context.write().unwrap().stack().pop::<i32>().unwrap(), 2);
687 }
688}