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