Skip to main content

qcvm/
userdata.rs

1//! Types and traits related to host bindings for the QuakeC runtime.
2
3use std::{
4    any::Any,
5    fmt,
6    ops::{Deref, DerefMut},
7    sync::Arc,
8};
9
10use anyhow::bail;
11use arrayvec::ArrayVec;
12use bump_scope::BumpScope;
13use snafu::Snafu;
14
15use crate::{
16    Address, AsErasedContext, BuiltinDef, CallArgs, ExecutionCtx, FunctionRef, MAX_ARGS, QCMemory,
17    QCParams, Type, Value, function_args,
18    progs::{VmScalar, VmValue},
19};
20
21/// A type-erased entity handle.
22#[repr(transparent)]
23#[derive(Debug, PartialEq, Eq, Copy, Clone)]
24pub struct ErasedEntityHandle(pub u64);
25
26type ErasedAddr = u16;
27
28/// An error when getting/setting an address
29#[derive(Snafu, Debug, Copy, Clone, PartialEq, Eq)]
30pub enum AddrError<E> {
31    /// The address does not exist
32    OutOfRange,
33    /// Another error occurred
34    Other {
35        /// The underlying error.
36        error: E,
37    },
38}
39
40impl<E> From<E> for AddrError<E> {
41    fn from(value: E) -> Self {
42        Self::Other { error: value }
43    }
44}
45
46impl<E> AddrError<E>
47where
48    E: fmt::Display,
49{
50    pub(crate) fn into_anyhow(self) -> AddrError<anyhow::Error> {
51        {
52            match self {
53                Self::OutOfRange => AddrError::OutOfRange,
54                Self::Other { error: e } => AddrError::Other {
55                    error: anyhow::format_err!("{e}"),
56                },
57            }
58        }
59    }
60}
61
62/// User-provided global context. This is passed in to all functions and entity getters/setters.
63pub trait Context {
64    /// The type of entity handles.
65    // TODO: It might be better to somehow handle this in a way that doesn't require wrapping it in an `Arc`,
66    //       as usually entity handles will just be a simple number.
67    type Entity: ?Sized + EntityHandle<Context = Self>;
68    /// The type of host-provided builtin functions.
69    type Function: ?Sized + Function<Context = Self>;
70    /// The error that is returned by [`Context::builtin`].
71    type Error: std::error::Error;
72    /// The type representing valid globals
73    type GlobalAddr: Address;
74
75    /// Given a function definition, get the builtin that it corresponds to (if one exists).
76    fn builtin(&self, def: &BuiltinDef) -> Result<Arc<Self::Function>, Self::Error>;
77
78    /// Get a global with the given definition
79    fn global(&self, def: Self::GlobalAddr) -> Result<Value, AddrError<Self::Error>>;
80
81    /// Implement the `OP_STATE` opcode.
82    ///
83    /// This expected behavior is as follows:
84    ///
85    /// - Set `self.nextthink` to `self.time + delta_time`, where `delta_time` is the time between
86    ///   frames.
87    /// - Set `self.frame` to the first argument.
88    /// - Set `self.think` to the second argument.
89    fn state(&self, _frame: f32, _think_fn: Arc<dyn ErasedFunction>) -> Result<(), Self::Error> {
90        unimplemented!("`OP_STATE` not available in this environment");
91    }
92
93    /// Set a global with the given definition
94    fn set_global(
95        &mut self,
96        def: Self::GlobalAddr,
97        value: Value,
98    ) -> Result<(), AddrError<Self::Error>>;
99}
100
101/// A type-erased context that can be used for dynamic dispatch.
102pub trait ErasedContext: Any {
103    /// Dynamic version of [`Context::builtin`].
104    fn dyn_builtin(&self, def: &BuiltinDef) -> anyhow::Result<Arc<dyn ErasedFunction>>;
105
106    /// Dynamic version of [`Context::state`].
107    fn dyn_state(&self, _frame: f32, _think_fn: Arc<dyn ErasedFunction>) -> anyhow::Result<()> {
108        anyhow::bail!("`OP_STATE` not available in this environment")
109    }
110
111    /// Dynamic version of `<Context::Entity as EntityHandle>::get`.
112    fn dyn_entity_get(
113        &self,
114        erased_ent: u64,
115        field: ErasedAddr,
116        ty: Type,
117    ) -> anyhow::Result<Value, AddrError<anyhow::Error>>;
118
119    /// Dynamic version of `<Context::Entity as EntityHandle>::set`.
120    fn dyn_entity_set(
121        &mut self,
122        erased_ent: u64,
123        field: ErasedAddr,
124        value: Value,
125    ) -> anyhow::Result<(), AddrError<anyhow::Error>>;
126
127    /// Dynamic version of [`Context::global`]
128    fn dyn_global(&self, def: ErasedAddr, ty: Type) -> Result<Value, AddrError<anyhow::Error>>;
129
130    /// Dynamic version of [`Context::set_global`]
131    fn dyn_set_global(
132        &mut self,
133        def: ErasedAddr,
134        value: Value,
135    ) -> Result<(), AddrError<anyhow::Error>>;
136}
137
138impl fmt::Debug for &'_ mut dyn ErasedContext {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        <&dyn ErasedContext>::fmt(&&**self, f)
141    }
142}
143
144impl fmt::Debug for &'_ dyn ErasedContext {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        write!(f, "..")
147    }
148}
149
150impl<T> ErasedContext for T
151where
152    T: Any + Context,
153    T::Function: Sized + ErasedFunction,
154{
155    fn dyn_builtin(&self, def: &BuiltinDef) -> anyhow::Result<Arc<dyn ErasedFunction>> {
156        Ok(self.builtin(def).map_err(|e| anyhow::format_err!("{e}"))? as Arc<dyn ErasedFunction>)
157    }
158
159    fn dyn_entity_get(
160        &self,
161        erased_ent: u64,
162        field: ErasedAddr,
163        ty: Type,
164    ) -> anyhow::Result<Value, AddrError<anyhow::Error>> {
165        let field_addr =
166            <<T::Entity as EntityHandle>::FieldAddr as Address>::from_u16_typed(field, ty)
167                .ok_or(AddrError::OutOfRange)?;
168        <T::Entity as EntityHandle>::from_erased(erased_ent, |ent| ent.get(self, field_addr))
169            .map_err(|e| anyhow::format_err!("{e}"))?
170            .map_err(|e| AddrError::Other {
171                error: anyhow::format_err!("{e}"),
172            })
173    }
174
175    fn dyn_entity_set(
176        &mut self,
177        erased_ent: u64,
178        field: ErasedAddr,
179        value: Value,
180    ) -> Result<(), AddrError<anyhow::Error>> {
181        let field_addr = <<T::Entity as EntityHandle>::FieldAddr as Address>::from_u16_typed(
182            field,
183            value.type_(),
184        )
185        .ok_or(AddrError::OutOfRange)?;
186        <T::Entity as EntityHandle>::from_erased(erased_ent, |ent| ent.set(self, field_addr, value))
187            .map_err(|e| anyhow::format_err!("{e}"))?
188            .map_err(|e| AddrError::Other {
189                error: anyhow::format_err!("{e}"),
190            })
191    }
192
193    fn dyn_global(&self, def: ErasedAddr, ty: Type) -> Result<Value, AddrError<anyhow::Error>> {
194        self.global(
195            <T as Context>::GlobalAddr::from_u16_typed(def, ty).ok_or(AddrError::OutOfRange)?,
196        )
197        .map_err(AddrError::into_anyhow)
198    }
199
200    fn dyn_set_global(
201        &mut self,
202        def: ErasedAddr,
203        value: Value,
204    ) -> Result<(), AddrError<anyhow::Error>> {
205        self.set_global(
206            <T as Context>::GlobalAddr::from_u16_typed(def, value.type_())
207                .ok_or(AddrError::OutOfRange)?,
208            value,
209        )
210        .map_err(AddrError::into_anyhow)
211    }
212}
213
214/// The type of values that can be used in the QuakeC runtime.
215///
216/// > *TODO*: Implement proper userdata support.
217pub trait QCType: fmt::Debug {
218    /// The QuakeC type of this value.
219    fn type_(&self) -> Type;
220    /// Whether this value should be considered null.
221    fn is_null(&self) -> bool;
222}
223
224/// A dynamic form of [`PartialEq`] that can be used in type-erased contexts.
225pub trait DynEq: Any {
226    /// Dynamic version of [`PartialEq::eq`].
227    fn dyn_eq(&self, other: &dyn Any) -> bool;
228    /// Dynamic version of [`PartialEq::ne`].
229    fn dyn_ne(&self, other: &dyn Any) -> bool {
230        !self.dyn_eq(other)
231    }
232}
233
234impl<T> DynEq for T
235where
236    T: PartialEq + Any,
237{
238    fn dyn_eq(&self, other: &dyn Any) -> bool {
239        other.downcast_ref().is_some_and(|other| self == other)
240    }
241
242    fn dyn_ne(&self, other: &dyn Any) -> bool {
243        other.downcast_ref().is_some_and(|other| self != other)
244    }
245}
246
247/// A handle to a host entity. This should _not_ be the type that stores the actual entity
248/// data. All values in `qcvm` are immutable, the only mutable state is the context. This
249/// should be implemented for a handle to an entity, with the entity data itself being stored
250/// in the context.
251pub trait EntityHandle: QCType {
252    /// The global context that holds the entity data.
253    type Context: ?Sized + Context<Entity = Self>;
254    /// The error returned from this
255    type Error: std::error::Error;
256    /// The type representing fields
257    type FieldAddr: Address;
258
259    /// Convert from an opaque handle to a reference to this type (must be a reference in order to allow unsized handles)
260    fn from_erased<F, O>(erased: u64, callback: F) -> Result<O, Self::Error>
261    where
262        F: FnOnce(&Self) -> O,
263    {
264        Self::from_erased_mut(erased, |this| callback(&*this))
265    }
266
267    /// Convert from an opaque handle to a mutable reference to this type (must be a reference in order to allow unsized handles)
268    fn from_erased_mut<F, O>(erased: u64, callback: F) -> Result<O, Self::Error>
269    where
270        F: FnOnce(&mut Self) -> O;
271
272    /// Convert this type to an opaque handle
273    fn to_erased(&self) -> u64;
274
275    /// Get a field given this handle and reference to the context.
276    fn get(
277        &self,
278        context: &Self::Context,
279        field: Self::FieldAddr,
280    ) -> Result<Value, AddrError<Self::Error>>;
281
282    /// Set a field given this handle and a mutable reference to the context.
283    fn set(
284        &self,
285        context: &mut Self::Context,
286        field: Self::FieldAddr,
287        value: Value,
288    ) -> Result<(), AddrError<Self::Error>>;
289}
290
291/// A function callable from QuakeC code. This may call further internal functions, and is
292/// passed a configurable context type.
293pub trait Function: QCType {
294    /// The user-provided context.
295    type Context: ?Sized + Context<Function = Self>;
296    /// The error returned by the methods in this trait.
297    type Error: std::error::Error;
298
299    /// Get the signature of the function. Note that only this number of arguments will
300    /// be passed to the function.
301    ///
302    /// > TODO: It may be useful to annotate the return type, too.
303    fn signature(&self) -> Result<ArrayVec<Type, MAX_ARGS>, Self::Error>;
304
305    /// Call the function.
306    fn call(&self, context: FnCall<'_, Self::Context>) -> Result<Value, Self::Error>;
307}
308
309/// The function call context, containing both the user context and the current VM context
310/// (which can be used to call into QuakeC functions).
311pub struct FnCall<'a, T: ?Sized = dyn ErasedContext> {
312    pub(crate) execution:
313        ExecutionCtx<'a, T, BumpScope<'a>, CallArgs<ArrayVec<[VmScalar; 3], MAX_ARGS>>>,
314}
315
316impl<T: ?Sized> Deref for FnCall<'_, T> {
317    type Target = T;
318
319    fn deref(&self) -> &Self::Target {
320        &*self.execution.context
321    }
322}
323
324impl<T: ?Sized> DerefMut for FnCall<'_, T> {
325    fn deref_mut(&mut self) -> &mut Self::Target {
326        &mut *self.execution.context
327    }
328}
329
330impl<T: ?Sized> FnCall<'_, T> {
331    /// Explicitly get a mutable reference to the user-provided context. This
332    /// is also available via the `Deref`/`DerefMut` implementation
333    pub fn context_mut(&mut self) -> &mut T {
334        self.execution.context
335    }
336}
337
338impl<T> FnCall<'_, T>
339where
340    T: ?Sized + AsErasedContext,
341{
342    /// Get an iterator of the arguments to this function. For now, the signature
343    /// must be explicitly provided.
344    ///
345    /// > TODO: The signature should not need to be passed here.
346    pub fn arguments(&self, args: &[Type]) -> impl Iterator<Item = Value> {
347        function_args()
348            .into_iter()
349            .zip(args)
350            .map(|(i, ty)| match ty {
351                Type::Vector => {
352                    let [x, y, z] = self.execution.memory.get_vector(i.addr as _).unwrap();
353                    let vec = [
354                        x.try_into().unwrap(),
355                        y.try_into().unwrap(),
356                        z.try_into().unwrap(),
357                    ];
358                    self.execution.to_value(VmValue::Vector(vec)).unwrap()
359                }
360                _ => {
361                    let value = self.execution.memory.get(i.addr as _).unwrap().into();
362                    self.execution.to_value(value).unwrap()
363                }
364            })
365    }
366}
367
368impl<'a> FnCall<'a, dyn ErasedContext> {
369    fn downcast<T>(self) -> Option<FnCall<'a, T>>
370    where
371        T: Any,
372    {
373        Some(FnCall {
374            execution: self.execution.downcast()?,
375        })
376    }
377}
378
379impl<'a, T> FnCall<'a, T>
380where
381    T: ErasedContext,
382{
383    /// Call a QuakeC function by index or name.
384    pub fn call<A, F>(&mut self, function_ref: F, args: A) -> anyhow::Result<Value>
385    where
386        F: Into<FunctionRef>,
387        A: QCParams,
388    {
389        let function_def = self
390            .execution
391            .functions
392            .get(function_ref)?
393            .clone()
394            .try_into_qc()
395            .map_err(|def| {
396                anyhow::format_err!("Function {:?} is not a QuakeC function", def.name)
397            })?;
398
399        self.execution
400            .with_args(&function_def.name, args, |mut exec| {
401                Ok(exec.execute_def(&function_def)?.try_into()?)
402            })
403    }
404}
405
406/// Type-erased version of [`Function`], for dynamic dispatch.
407pub trait ErasedFunction: QCType + Send + Sync + DynEq {
408    /// Dynamic version of [`Function::signature`].
409    fn dyn_signature(&self) -> anyhow::Result<ArrayVec<Type, MAX_ARGS>>;
410
411    /// Dynamic version of [`Function::call`].
412    fn dyn_call<'a, 'b>(&'a self, context: FnCall<'b>) -> anyhow::Result<Value>;
413}
414
415impl PartialEq for dyn ErasedFunction {
416    fn eq(&self, other: &Self) -> bool {
417        self.dyn_eq(other)
418    }
419}
420
421impl QCType for ErasedEntityHandle {
422    fn type_(&self) -> Type {
423        Type::Entity
424    }
425
426    fn is_null(&self) -> bool {
427        false
428    }
429}
430
431impl<T> ErasedFunction for T
432where
433    T: Function + PartialEq + Any + Send + Sync,
434    T::Context: Sized,
435{
436    fn dyn_signature(&self) -> anyhow::Result<ArrayVec<Type, MAX_ARGS>> {
437        self.signature().map_err(|e| anyhow::format_err!("{e}"))
438    }
439
440    fn dyn_call(&self, context: FnCall) -> anyhow::Result<Value> {
441        let type_name = std::any::type_name_of_val(context.execution.context);
442        match context.downcast() {
443            Some(context) => Ok(self.call(context).map_err(|e| anyhow::format_err!("{e}"))?),
444            None => bail!(
445                "Type mismatch for builtin context: expected {}, found {}",
446                std::any::type_name::<T::Context>(),
447                type_name
448            ),
449        }
450    }
451}