use alloc::vec::Vec;
use alloc::boxed::Box;
use alloc::collections::{VecDeque, BTreeMap};
use alloc::rc::Rc;
use crate::*;
use crate::gc::*;
use crate::slotmap::*;
use crate::runtime::*;
use crate::bytecode::*;
use crate::process::*;
use crate::compact_str::*;
use crate::vecmap::*;
new_key! {
struct ProcessKey;
}
pub struct IdleAction {
count: usize,
thresh: usize,
action: Box<dyn FnMut()>,
}
impl IdleAction {
pub fn new(thresh: usize, action: Box<dyn FnMut()>) -> Self {
Self { count: 0, thresh, action }
}
pub fn consume<C: CustomTypes<S>, S: System<C>>(&mut self, res: &ProjectStep<'_, C, S>) {
match res {
ProjectStep::Idle | ProjectStep::Yield | ProjectStep::Pause => {
self.count += 1;
if self.count >= self.thresh {
self.trigger();
}
}
ProjectStep::Normal | ProjectStep::ProcessTerminated { .. } | ProjectStep::Error { .. } | ProjectStep::Watcher { .. } => self.count = 0,
}
}
pub fn trigger(&mut self) {
self.count = 0;
self.action.as_mut()();
}
}
#[derive(Debug)]
pub enum Input {
Start,
Stop,
KeyDown { key: KeyCode },
KeyUp { key: KeyCode },
CustomEvent { name: CompactString, args: VecMap<CompactString, SimpleValue, false>, interrupt: bool, max_queue: usize },
}
pub enum ProjectStep<'gc, C: CustomTypes<S>, S: System<C>> {
Idle,
Yield,
Normal,
ProcessTerminated { result: Value<'gc, C, S>, proc: Process<'gc, C, S> },
Error { error: ExecError<C, S>, proc: Process<'gc, C, S> },
Watcher { create: bool, watcher: Watcher<'gc, C, S> },
Pause,
}
#[derive(Collect)]
#[collect(no_drop, bound = "")]
pub struct PartialProcContext<'gc, C: CustomTypes<S>, S: System<C>> {
pub locals: SymbolTable<'gc, C, S>,
#[collect(require_static)] pub state: C::ProcessState,
#[collect(require_static)] pub barrier: Option<Barrier>,
#[collect(require_static)] pub reply_key: Option<InternReplyKey>,
#[collect(require_static)] pub local_message: Option<CompactString>,
}
#[derive(Collect)]
#[collect(no_drop, bound = "")]
struct Script<'gc, C: CustomTypes<S>, S: System<C>> {
#[collect(require_static)] event: Rc<(Event, usize)>, entity: Gc<'gc, RefLock<Entity<'gc, C, S>>>,
#[collect(require_static)] process: Option<ProcessKey>,
context_queue: VecDeque<PartialProcContext<'gc, C, S>>,
}
impl<'gc, C: CustomTypes<S>, S: System<C>> Script<'gc, C, S> {
fn consume_context(&mut self, state: &mut State<'gc, C, S>) {
let process = self.process.and_then(|key| Some((key, state.processes.get_mut(key)?)));
match process {
Some(proc) => match proc.1.is_running() {
true => return,
false => unreachable!(), }
None => match self.context_queue.pop_front() {
None => return,
Some(context) => {
let proc = Process::new(ProcContext { global_context: state.global_context, entity: self.entity, state: context.state, start_pos: self.event.1, locals: context.locals, barrier: context.barrier, reply_key: context.reply_key, local_message: context.local_message });
let key = state.processes.insert(proc);
state.process_queue.push_back(key);
self.process = Some(key);
}
},
}
}
fn stop_all(&mut self, state: &mut State<'gc, C, S>) {
if let Some(process) = self.process.take() {
state.processes.remove(process);
}
self.context_queue.clear();
}
fn schedule(&mut self, state: &mut State<'gc, C, S>, context: PartialProcContext<'gc, C, S>, max_queue: usize) {
self.context_queue.push_back(context);
self.consume_context(state);
if self.context_queue.len() > max_queue {
self.context_queue.pop_back();
}
}
}
struct AllContextsConsumer {
did_it: bool,
}
impl AllContextsConsumer {
fn new() -> Self {
Self { did_it: false }
}
fn do_once<C: CustomTypes<S>, S: System<C>>(&mut self, proj: &mut Project<C, S>) {
if !core::mem::replace(&mut self.did_it, true) {
for script in proj.scripts.iter_mut() {
script.consume_context(&mut proj.state);
}
}
}
}
#[derive(Collect)]
#[collect(no_drop, bound = "")]
struct State<'gc, C: CustomTypes<S>, S: System<C>> {
global_context: Gc<'gc, RefLock<GlobalContext<'gc, C, S>>>,
processes: SlotMap<ProcessKey, Process<'gc, C, S>>,
#[collect(require_static)] process_queue: VecDeque<ProcessKey>,
}
#[derive(Collect)]
#[collect(no_drop, bound = "")]
pub struct Project<'gc, C: CustomTypes<S>, S: System<C>> {
state: State<'gc, C, S>,
scripts: Vec<Script<'gc, C, S>>,
}
impl<'gc, C: CustomTypes<S>, S: System<C>> Project<'gc, C, S> {
pub fn from_init(mc: &Mutation<'gc>, init_info: &InitInfo, bytecode: Rc<ByteCode>, settings: Settings, system: Rc<S>) -> Self {
let global_context = GlobalContext::from_init(mc, init_info, bytecode, settings, system);
let mut project = Self::new(Gc::new(mc, RefLock::new(global_context)));
for entity_info in init_info.entities.iter() {
let entity = *project.state.global_context.borrow().entities.get(&entity_info.name).unwrap();
for (event, pos) in entity_info.scripts.iter() {
project.add_script(*pos, entity, event.clone());
}
}
project
}
pub fn new(global_context: Gc<'gc, RefLock<GlobalContext<'gc, C, S>>>) -> Self {
Self {
state: State {
global_context,
processes: Default::default(),
process_queue: Default::default(),
},
scripts: Default::default(),
}
}
pub fn add_script(&mut self, start_pos: usize, entity: Gc<'gc, RefLock<Entity<'gc, C, S>>>, event: Event) {
self.scripts.push(Script {
event: Rc::new((event, start_pos)),
entity,
process: None,
context_queue: Default::default(),
});
}
pub fn input(&mut self, mc: &Mutation<'gc>, input: Input) {
let mut all_contexts_consumer = AllContextsConsumer::new();
match input {
Input::Start => {
for i in 0..self.scripts.len() {
if let Event::OnFlag = &self.scripts[i].event.0 {
let state = C::ProcessState::from(ProcessKind { entity: self.scripts[i].entity, dispatcher: None });
all_contexts_consumer.do_once(self); self.scripts[i].stop_all(&mut self.state);
self.scripts[i].schedule(&mut self.state, PartialProcContext { state, locals: Default::default(), barrier: None, reply_key: None, local_message: None }, 0);
}
}
}
Input::CustomEvent { name, args, interrupt, max_queue } => {
for i in 0..self.scripts.len() {
if let Event::Custom { name: script_event_name, fields } = &self.scripts[i].event.0 {
if name != *script_event_name { continue }
let mut locals = SymbolTable::default();
for field in fields.iter() {
let value = args.get(field).map(|x| Value::from_simple(mc, x.clone())).unwrap_or_else(|| Number::new(0.0).unwrap().into());
locals.define_or_redefine(field, value.into());
}
let state = C::ProcessState::from(ProcessKind { entity: self.scripts[i].entity, dispatcher: None });
all_contexts_consumer.do_once(self); if interrupt { self.scripts[i].stop_all(&mut self.state); }
self.scripts[i].schedule(&mut self.state, PartialProcContext { locals, state, barrier: None, reply_key: None, local_message: None }, max_queue);
}
}
}
Input::Stop => {
for script in self.scripts.iter_mut() {
script.stop_all(&mut self.state);
}
self.state.processes.clear();
self.state.process_queue.clear();
}
Input::KeyDown { key: input_key } => {
for i in 0..self.scripts.len() {
if let Event::OnKey { key_filter } = &self.scripts[i].event.0 {
if key_filter.map(|x| x == input_key).unwrap_or(true) {
let state = C::ProcessState::from(ProcessKind { entity: self.scripts[i].entity, dispatcher: None });
all_contexts_consumer.do_once(self); self.scripts[i].schedule(&mut self.state, PartialProcContext { state, locals: Default::default(), barrier:None, reply_key: None, local_message: None }, 0);
}
}
}
}
Input::KeyUp { .. } => unimplemented!(),
}
}
pub fn step(&mut self, mc: &Mutation<'gc>) -> ProjectStep<'gc, C, S> {
let mut all_contexts_consumer = AllContextsConsumer::new();
let msg = self.state.global_context.borrow().system.receive_message();
if let Some(IncomingMessage { msg_type, values, reply_key }) = msg {
let values: BTreeMap<_,_> = values.into_iter().collect();
for i in 0..self.scripts.len() {
if let Event::NetworkMessage { msg_type: script_msg_type, fields } = &self.scripts[i].event.0 {
if msg_type != *script_msg_type { continue }
let mut locals = SymbolTable::default();
for field in fields.iter() {
let value = values.get(field).map(|x| Value::from_simple(mc, x.clone())).unwrap_or_else(|| Number::new(0.0).unwrap().into());
locals.define_or_redefine(field, value.into());
}
let state = C::ProcessState::from(ProcessKind { entity: self.scripts[i].entity, dispatcher: None });
all_contexts_consumer.do_once(self); self.scripts[i].schedule(&mut self.state, PartialProcContext { locals, state, barrier: None, reply_key: reply_key.clone(), local_message: None }, usize::MAX);
}
}
}
let (proc_key, proc) = loop {
match self.state.process_queue.pop_front() {
None => {
debug_assert!(self.scripts.iter().all(|x| x.context_queue.is_empty()));
return ProjectStep::Idle;
}
Some(proc_key) => if let Some(proc) = self.state.processes.get_mut(proc_key) { break (proc_key, proc) }
}
};
match proc.step(mc) {
Ok(x) => match x {
ProcessStep::Normal => {
self.state.process_queue.push_front(proc_key);
ProjectStep::Normal
}
ProcessStep::Yield => {
all_contexts_consumer.do_once(self); self.state.process_queue.push_back(proc_key);
ProjectStep::Yield
}
ProcessStep::Watcher { create, watcher } => {
self.state.process_queue.push_front(proc_key);
ProjectStep::Watcher { create, watcher }
}
ProcessStep::Pause => {
self.state.process_queue.push_front(proc_key);
ProjectStep::Pause
}
ProcessStep::Fork { pos, locals, entity } => {
let state = C::ProcessState::from(ProcessKind { entity, dispatcher: Some(proc) });
let proc = Process::new(ProcContext { global_context: self.state.global_context, entity, state, start_pos: pos, locals, barrier: None, reply_key: None, local_message: None });
let fork_proc_key = self.state.processes.insert(proc);
all_contexts_consumer.do_once(self); self.state.process_queue.push_back(fork_proc_key); self.state.process_queue.push_front(proc_key); ProjectStep::Normal
}
ProcessStep::CreatedClone { clone } => {
let original = clone.borrow().original.unwrap();
let mut new_scripts = vec![];
for script in self.scripts.iter() {
if Gc::ptr_eq(script.entity, original) {
new_scripts.push(Script {
event: script.event.clone(),
entity: clone,
process: None,
context_queue: Default::default(),
});
}
}
for script in new_scripts.iter_mut() {
if let Event::OnClone = &script.event.0 {
let state = C::ProcessState::from(ProcessKind { entity: script.entity, dispatcher: Some(self.state.processes.get(proc_key).unwrap()) });
all_contexts_consumer.do_once(self); script.schedule(&mut self.state, PartialProcContext { state, locals: Default::default(), barrier: None, reply_key: None, local_message: None }, 0);
}
}
self.scripts.extend(new_scripts);
self.state.process_queue.push_front(proc_key); ProjectStep::Normal
}
ProcessStep::DeletedClone { clone } => {
debug_assert!(clone.borrow().original.is_some());
self.scripts.retain_mut(|script| !Gc::ptr_eq(script.entity, clone));
self.state.processes.retain_mut(|_, proc| !Gc::ptr_eq(proc.get_call_stack().first().unwrap().entity, clone));
self.state.process_queue.push_front(proc_key); ProjectStep::Normal
}
ProcessStep::Broadcast { msg_type, barrier, targets } => {
for i in 0..self.scripts.len() {
if let Event::LocalMessage { msg_type: recv_type } = &self.scripts[i].event.0 {
if recv_type.as_ref().map(|x| *x == msg_type).unwrap_or(true) {
if let Some(targets) = &targets {
if !targets.iter().any(|&target| Gc::ptr_eq(self.scripts[i].entity, target)) {
continue
}
}
let state = C::ProcessState::from(ProcessKind { entity: self.scripts[i].entity, dispatcher: Some(self.state.processes.get(proc_key).unwrap()) });
all_contexts_consumer.do_once(self); self.scripts[i].stop_all(&mut self.state);
self.scripts[i].schedule(&mut self.state, PartialProcContext { state, locals: Default::default(), barrier: barrier.clone(), reply_key: None, local_message: Some(msg_type.clone()) }, 0);
}
}
}
self.state.process_queue.push_front(proc_key); ProjectStep::Normal
}
ProcessStep::Terminate { result } => {
let proc = self.state.processes.remove(proc_key).unwrap();
all_contexts_consumer.do_once(self); ProjectStep::ProcessTerminated { result, proc }
}
ProcessStep::Abort { mode } => match mode {
AbortMode::Current => {
debug_assert!(!proc.is_running());
self.state.processes.remove(proc_key);
ProjectStep::Normal
}
AbortMode::All => {
debug_assert!(!proc.is_running());
self.state.processes.clear();
self.state.process_queue.clear();
ProjectStep::Normal
}
AbortMode::Others => {
debug_assert!(proc.is_running());
self.state.processes.retain_mut(|k, _| k == proc_key);
debug_assert_eq!(self.state.processes.len(), 1);
self.state.process_queue.clear();
self.state.process_queue.push_front(proc_key); ProjectStep::Normal
}
AbortMode::MyOthers => {
debug_assert!(proc.is_running());
let entity = proc.get_call_stack().last().unwrap().entity;
self.state.processes.retain_mut(|k, v| k == proc_key || !Gc::ptr_eq(entity, v.get_call_stack().first().unwrap().entity));
self.state.process_queue.push_front(proc_key); ProjectStep::Normal
}
}
ProcessStep::Idle => unreachable!(),
}
Err(error) => {
let proc = self.state.processes.remove(proc_key).unwrap();
all_contexts_consumer.do_once(self); ProjectStep::Error { error, proc }
}
}
}
pub fn get_global_context(&self) -> Gc<'gc, RefLock<GlobalContext<'gc, C, S>>> {
self.state.global_context
}
}