fromsoftware_shared/
task.rs

1use std::{
2    cell::UnsafeCell,
3    ffi::c_void,
4    sync::{
5        Arc,
6        atomic::{AtomicBool, Ordering},
7    },
8};
9
10use vtable_rs::VPtr;
11
12/// The trait shared by task implementations across FSW games.
13pub trait SharedTaskImp<TIndex, TTaskData: Send + 'static> {
14    /// Directly calls the internal task registration function. Users should not
15    /// call this directly.
16    fn register_task_internal(&self, index: TIndex, task: &RecurringTask<TTaskData>);
17}
18
19/// An extension on each game's task implementation to allow users to easily
20/// register custom tasks as Rust closures.
21pub trait SharedTaskImpExt<TIndex, TTaskData: Send + 'static> {
22    /// Registers the given closure as a task to the games task runtime.
23    fn run_recurring<T: Into<RecurringTask<TTaskData>>>(
24        &self,
25        execute: T,
26        group: TIndex,
27    ) -> RecurringTaskHandle<TTaskData>;
28}
29
30impl<TIndex, TTaskData: Send + 'static, S: SharedTaskImp<TIndex, TTaskData>>
31    SharedTaskImpExt<TIndex, TTaskData> for S
32{
33    fn run_recurring<T: Into<RecurringTask<TTaskData>>>(
34        &self,
35        task: T,
36        group: TIndex,
37    ) -> RecurringTaskHandle<TTaskData> {
38        #[allow(clippy::arc_with_non_send_sync)]
39        let task: Arc<RecurringTask<TTaskData>> = Arc::new(task.into());
40        // SAFETY: we hold a unique reference to the contents of `arc`
41        unsafe {
42            *task.self_ref.get() = Some(task.clone());
43        }
44
45        self.register_task_internal(group, task.as_ref());
46
47        RecurringTaskHandle { _task: task }
48    }
49}
50
51/// A handle for the a task registered through `SharedTaskImpExt.run_recurring`
52/// that allows users to cancel it later using `Drop.drop`.
53pub struct RecurringTaskHandle<TTaskData: Send + 'static> {
54    _task: Arc<RecurringTask<TTaskData>>,
55}
56
57impl<TTaskData: Send + 'static> Drop for RecurringTaskHandle<TTaskData> {
58    fn drop(&mut self) {
59        self._task.cancel();
60    }
61}
62
63/// A custom task created by `fromsoftware-rs` that can masquerade as one of the
64/// game's native tasks.
65///
66/// We assume that the subset of this structure that the games care (especially
67/// the vftable) about is the same across games. So far, we know it works on both
68/// DS3 and ER.
69#[repr(C)]
70pub struct RecurringTask<TTaskData: Send + 'static> {
71    vftable: VPtr<dyn SharedTaskBaseVmt, Self>,
72    unk8: usize,
73    closure: Box<dyn FnMut(&TTaskData)>,
74    unregister_requested: AtomicBool,
75    self_ref: UnsafeCell<Option<Arc<Self>>>,
76}
77
78impl<TTaskData: Send + 'static> RecurringTask<TTaskData> {
79    pub fn new<F: FnMut(&TTaskData) + 'static + Send>(closure: F) -> Self {
80        Self {
81            vftable: Default::default(),
82            unk8: 0,
83            closure: Box::new(closure),
84            unregister_requested: AtomicBool::new(false),
85            self_ref: UnsafeCell::new(None),
86        }
87    }
88
89    pub fn cancel(&self) {
90        self.unregister_requested.store(true, Ordering::Relaxed);
91    }
92}
93
94impl<TTaskData: Send + 'static> SharedTaskBaseVmt for RecurringTask<TTaskData> {
95    extern "C" fn get_runtime_class(&self) -> usize {
96        unimplemented!();
97    }
98
99    extern "C" fn destructor(&mut self) {
100        unimplemented!();
101    }
102
103    extern "C" fn execute(&mut self, data: *const c_void) {
104        // Run the task if cancellation wasn't requested.
105        // if !self.unregister_requested.load(Ordering::Relaxed) {
106
107        // SAFETY: We're declaring the type of the data in the first place.
108        (self.closure)(unsafe { &*(data as *const TTaskData) });
109
110        // }
111
112        // TODO: implement the games unregister fn to properly get the task removed from the task
113        // pool instead of just not running the closure.
114
115        // Drop if we got cancelled in the meanwhile.
116        // if self.unregister_requested.load(Ordering::Relaxed) {
117        //     self.self_ref.get_mut().take();
118        // }
119    }
120}
121
122impl<TTaskData: Send + 'static, F: FnMut(&TTaskData) + 'static + Send> From<F>
123    for RecurringTask<TTaskData>
124{
125    fn from(value: F) -> Self {
126        Self::new(value)
127    }
128}
129
130#[vtable_rs::vtable]
131trait SharedTaskBaseVmt {
132    fn get_runtime_class(&self) -> usize;
133
134    fn destructor(&mut self);
135
136    // TODO: Make data generic once vtable-rs supports this.
137    fn execute(&mut self, data: *const c_void);
138}