From 39d53b08faffe7cde4db8d486b69ad3ec74208a4 Mon Sep 17 00:00:00 2001 From: Dmytro Lysai Date: Fri, 5 Jun 2020 13:17:13 +0300 Subject: [PATCH] [script] Add support for instantiating scripts within scripts --- src/game/script.rs | 150 ++++++++++++++++++++++++++++++--------------- src/vm.rs | 11 ++-- 2 files changed, 108 insertions(+), 53 deletions(-) diff --git a/src/game/script.rs b/src/game/script.rs index b92b49f..a03e51a 100644 --- a/src/game/script.rs +++ b/src/game/script.rs @@ -1,5 +1,6 @@ use bstring::BString; use byteorder::{BigEndian, ReadBytesExt}; +use enum_map::EnumMap; use enum_map_derive::Enum; use enum_primitive_derive::Primitive; use num_traits::FromPrimitive; @@ -15,6 +16,7 @@ use crate::asset::proto::ProtoDb; use crate::asset::script::ProgramId; use crate::asset::script::db::ScriptDb; use crate::game::object; +use crate::util::EnumExt; use crate::vm::{self, *}; use crate::vm::value::Value; @@ -185,6 +187,52 @@ pub struct Script { pub object: Option, } +/// Interface for instantiating new scripts from within a script context. +/// The instantiation itself is deferred until the script procedure returns. +pub struct NewScripts { + unused_sids: EnumMap, + new_scripts: Vec<(ScriptIId, ProgramId)>, +} + +impl NewScripts { + fn new(scripts: &Scripts) -> Self { + let mut unused_sids = EnumMap::from(|k| ScriptIId::new(k, 0)); + for &sid in scripts.scripts.keys() { + if sid.id() > unused_sids[sid.kind()].id() { + unused_sids[sid.kind()] = sid; + } + } + let mut r = Self { unused_sids, new_scripts: Vec::new() }; + for k in ScriptKind::iter() { + r.bump(k); + } + r + } + + #[must_use] + pub fn new_script(&mut self, kind: ScriptKind, prg_id: ProgramId) -> ScriptIId { + let sid = self.unused_sid(kind); + self.bump(kind); + self.new_scripts.push((sid, prg_id)); + sid + } + + fn unused_sid(&self, kind: ScriptKind) -> ScriptIId { + self.unused_sids[kind] + } + + fn bump(&mut self, kind: ScriptKind) { + let cur = self.unused_sids[kind].id(); + self.unused_sids[kind] = ScriptIId::new(kind, cur.checked_add(1).unwrap()); + } + + fn instantiate(self, scripts: &mut Scripts) { + for (sid, prg_id) in self.new_scripts { + scripts.instantiate(sid, prg_id, None).unwrap(); + } + } +} + pub struct Scripts { proto_db: Rc, db: ScriptDb, @@ -260,7 +308,7 @@ impl Scripts { object: None, }); if let Some(existing) = existing { - panic!("{:?} #{} duplicates existing #{}", + panic!("{:?} program #{} duplicates existing program #{}", sid, program_id.val(), existing.program_id.val()); } Ok(()) @@ -268,7 +316,7 @@ impl Scripts { pub fn instantiate_map_script(&mut self, program_id: ProgramId) -> io::Result { assert!(self.map_sid.is_none()); - let sid = self.next_sid(ScriptKind::System); + let sid = NewScripts::new(self).unused_sid(ScriptKind::System); self.instantiate(sid, program_id, None)?; self.map_sid = Some(sid); Ok(sid) @@ -285,34 +333,40 @@ impl Scripts { pub fn execute_proc(&mut self, sid: ScriptIId, proc_id: ProcedureId, ctx: &mut Context) -> InvocationResult { - let script = self.scripts.get_mut(&sid).unwrap(); - let vm_ctx = &mut Self::make_vm_ctx( - &mut script.local_vars, - &mut self.vars, - &mut self.db, - &self.proto_db, - script.object, - ctx); - if !script.inited { - debug!("[{:?}#{}:{}] running program initialization code", + let (r, new_scripts) = { + let new_scripts = NewScripts::new(self); + let script = self.scripts.get_mut(&sid).unwrap(); + let mut vm_ctx = Self::make_vm_ctx( + &mut script.local_vars, + &mut self.vars, + &mut self.db, + new_scripts, + &self.proto_db, + script.object, + ctx); + if !script.inited { + debug!("[{:?}#{}:{}] running program initialization code", + sid, + script.program_id.val(), + self.vm.program_state(script.program).program().name()); + self.vm.run(script.program, &mut vm_ctx).unwrap() + .assert_no_suspend(); + script.inited = true; + } + let prg = self.vm.program_state_mut(script.program); + debug!("[{:?}#{}:{}] executing proc {:?} ({:?})", sid, script.program_id.val(), - self.vm.program_state(script.program).program().name()); - self.vm.run(script.program, vm_ctx).unwrap() - .assert_no_suspend(); - script.inited = true; - } - let prg = self.vm.program_state_mut(script.program); - debug!("[{:?}#{}:{}] executing proc {:?} ({:?})", - sid, - script.program_id.val(), - prg.program().name(), - proc_id, - prg.program().proc(proc_id).map(|p| p.name())); - let r = prg.execute_proc(proc_id, vm_ctx).unwrap(); - if r.suspend.is_some() { - self.suspend_stack.push(sid); - } + prg.program().name(), + proc_id, + prg.program().proc(proc_id).map(|p| p.name())); + let r = prg.execute_proc(proc_id, &mut vm_ctx).unwrap(); + if r.suspend.is_some() { + self.suspend_stack.push(sid); + } + (r, vm_ctx.new_scripts) + }; + new_scripts.instantiate(self); r } @@ -383,27 +437,23 @@ impl Scripts { } pub fn resume(&mut self, ctx: &mut Context) -> InvocationResult { - let sid = self.suspend_stack.pop().unwrap(); - let script = self.scripts.get_mut(&sid).unwrap(); - let vm_ctx = &mut Self::make_vm_ctx( - &mut script.local_vars, - &mut self.vars, - &mut self.db, - &self.proto_db, - script.object, - ctx); - self.vm.program_state_mut(script.program).resume(vm_ctx).unwrap() - } - - fn next_sid(&self, kind: ScriptKind) -> ScriptIId { - let id = self.scripts.keys() - .cloned() - .filter(|sid| sid.kind() == kind) - .map(|sid| sid.id()) - .max() - .map(|v| v + 1) - .unwrap_or(0); - ScriptIId::new(kind, id) + let (r, new_scripts) = { + let sid = self.suspend_stack.pop().unwrap(); + let new_scripts = NewScripts::new(self); + let script = self.scripts.get_mut(&sid).unwrap(); + let mut vm_ctx = Self::make_vm_ctx( + &mut script.local_vars, + &mut self.vars, + &mut self.db, + new_scripts, + &self.proto_db, + script.object, + ctx); + let r = self.vm.program_state_mut(script.program).resume(&mut vm_ctx).unwrap(); + (r, vm_ctx.new_scripts) + }; + new_scripts.instantiate(self); + r } #[inline] @@ -411,6 +461,7 @@ impl Scripts { local_vars: &'a mut [i32], vars: &'a mut Vars, script_db: &'a mut ScriptDb, + new_scripts: NewScripts, proto_db: &'a ProtoDb, self_obj: Option, ctx: &'a mut Context, @@ -430,6 +481,7 @@ impl Scripts { dialog: ctx.dialog, message_panel: ctx.message_panel, script_db, + new_scripts, proto_db, map_id: ctx.map_id, rpg: ctx.rpg, diff --git a/src/vm.rs b/src/vm.rs index db30cf1..0917fb4 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -104,13 +104,15 @@ use std::rc::Rc; use std::str; use std::time::Duration; -pub use error::*; -pub use value::Value; +use crate::game::object; +use crate::game::script::{NewScripts, ScriptKind}; +use crate::util::SmKey; use instruction::{Instruction, instruction_map, Opcode}; use stack::{Stack, StackId}; -use crate::game::object; -use crate::util::SmKey; + +pub use error::*; +pub use value::Value; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum PredefinedProc { @@ -225,6 +227,7 @@ pub struct Context<'a> { pub dialog: &'a mut Option, pub message_panel: crate::ui::Handle, pub script_db: &'a mut crate::asset::script::db::ScriptDb, + pub new_scripts: NewScripts, pub proto_db: &'a crate::asset::proto::ProtoDb, pub map_id: crate::asset::map::MapId, pub rpg: &'a mut crate::game::rpg::Rpg,