npc_engine_core/
task.rs

1/*
2 *  SPDX-License-Identifier: Apache-2.0 OR MIT
3 *  © 2020-2022 ETH Zurich and other contributors, see AUTHORS.txt for details
4 */
5
6use std::{
7    hash::{Hash, Hasher},
8    num::NonZeroU64,
9};
10
11use downcast_rs::{impl_downcast, Downcast};
12
13use crate::{impl_task_boxed_methods, AgentId, Domain, StateDiffRef, StateDiffRefMut};
14
15/// The duration of a task, in ticks.
16pub type TaskDuration = u64;
17
18/// Transforms the debug string of a task to a string that can safely be used for filenames.
19pub fn debug_name_to_filename_safe(debug_name: &str) -> String {
20    debug_name
21        .replace(' ', "")
22        .replace('(', "")
23        .replace(')', "")
24        .replace('{', "_")
25        .replace('}', "")
26        .replace(' ', "_")
27        .replace(':', "_")
28        .replace(',', "_")
29}
30
31/// A task that modifies the state.
32///
33/// It is illegal to have a task of both 0-duration and not modifying the state,
34/// as this would lead to self-looping nodes in the planner.
35pub trait Task<D: Domain>: std::fmt::Debug + Downcast + Send + Sync {
36    /// Returns the relative weight of the task for the given agent in the given tick and world state, by default weight is 1.0.
37    fn weight(&self, _tick: u64, _state_diff: StateDiffRef<D>, _agent: AgentId) -> f32 {
38        1.0
39    }
40
41    /// Returns the duration of the task, for a given agent in a given tick and world state.
42    fn duration(&self, _tick: u64, _state_diff: StateDiffRef<D>, _agent: AgentId) -> TaskDuration;
43
44    /// Executes one step of the task for the given agent on the given tick and world state.
45    fn execute(
46        &self,
47        tick: u64,
48        state_diff: StateDiffRefMut<D>,
49        agent: AgentId,
50    ) -> Option<Box<dyn Task<D>>>;
51
52    /// Returns if the task is valid for the given agent in the given tick and world state.
53    fn is_valid(&self, tick: u64, state_diff: StateDiffRef<D>, agent: AgentId) -> bool;
54
55    /// Returns the display actions corresponding to this task.
56    fn display_action(&self) -> D::DisplayAction;
57
58    /// Utility method for cloning, since `Self: Clone` is not object-safe.
59    ///
60    /// Use the macro [impl_task_boxed_methods] to automatically generate this method.
61    fn box_clone(&self) -> Box<dyn Task<D>>;
62
63    /// Utility method for hashing, since `Self: Hash` is not object-safe.
64    ///
65    /// Use the macro [impl_task_boxed_methods] to automatically generate this method.
66    fn box_hash(&self, state: &mut dyn Hasher);
67
68    /// Utility method for equality, since trait objects are not inherently `Eq`.
69    ///
70    /// Should perform downcast to current type and then check equality.
71    ///
72    /// Use the macro [impl_task_boxed_methods] to automatically generate this method.
73    #[allow(clippy::borrowed_box)]
74    fn box_eq(&self, other: &Box<dyn Task<D>>) -> bool;
75}
76
77/// An idle task of duration 1 that is used by the planner when the task of an agent is not known.
78#[derive(Debug, Hash, Clone, PartialEq)]
79pub struct IdleTask;
80
81impl<D: Domain> Task<D> for IdleTask {
82    fn weight(&self, _tick: u64, _state_diff: StateDiffRef<D>, _agent: AgentId) -> f32 {
83        1f32
84    }
85
86    fn duration(&self, _tick: u64, _state_diff: StateDiffRef<D>, _agent: AgentId) -> TaskDuration {
87        1
88    }
89
90    fn execute(
91        &self,
92        _tick: u64,
93        _state_diff: StateDiffRefMut<D>,
94        _agent: AgentId,
95    ) -> Option<Box<dyn Task<D>>> {
96        None
97    }
98
99    fn is_valid(&self, _tick: u64, _state_diff: StateDiffRef<D>, _agent: AgentId) -> bool {
100        true
101    }
102
103    fn display_action(&self) -> D::DisplayAction {
104        D::display_action_task_idle()
105    }
106
107    impl_task_boxed_methods!(D);
108}
109
110/// A task to represent planning in the planning tree, if these need to be represented.
111#[derive(Clone, Debug, Hash, PartialEq, Eq)]
112pub struct PlanningTask(
113    /// The duration of the planning task
114    pub NonZeroU64,
115);
116
117impl<D: Domain> Task<D> for PlanningTask {
118    fn weight(&self, _tick: u64, _state_diff: StateDiffRef<D>, _agent: AgentId) -> f32 {
119        1f32
120    }
121
122    fn duration(&self, _tick: u64, _state_diff: StateDiffRef<D>, _agent: AgentId) -> TaskDuration {
123        self.0.get()
124    }
125
126    fn execute(
127        &self,
128        _tick: u64,
129        _state_diff: StateDiffRefMut<D>,
130        _agent: AgentId,
131    ) -> Option<Box<dyn Task<D>>> {
132        None
133    }
134
135    fn is_valid(&self, _tick: u64, _state_diff: StateDiffRef<D>, _agent: AgentId) -> bool {
136        true
137    }
138
139    fn display_action(&self) -> D::DisplayAction {
140        D::display_action_task_planning()
141    }
142
143    impl_task_boxed_methods!(D);
144}
145
146impl_downcast!(Task<D> where D: Domain);
147
148impl<D: Domain> Clone for Box<dyn Task<D>> {
149    fn clone(&self) -> Self {
150        self.box_clone()
151    }
152}
153
154impl<D: Domain> Hash for Box<dyn Task<D>> {
155    fn hash<H>(&self, state: &mut H)
156    where
157        H: Hasher,
158    {
159        self.box_hash(state);
160    }
161}
162
163impl<D: Domain> PartialEq for Box<dyn Task<D>> {
164    fn eq(&self, other: &Self) -> bool {
165        self.box_eq(other)
166    }
167}
168
169impl<D: Domain> Eq for Box<dyn Task<D>> {}
170
171/// Task implementors can use this macro to implement the `box_clone`, `box_hash` and `box_eq` functions.
172///
173/// The parameter is the name of your [Domain] struct.
174#[macro_export]
175macro_rules! impl_task_boxed_methods {
176    ($domain: ty) => {
177        fn box_clone(&self) -> Box<dyn Task<$domain>> {
178            Box::new(self.clone())
179        }
180
181        fn box_hash(&self, mut state: &mut dyn std::hash::Hasher) {
182            use std::hash::Hash;
183            self.hash(&mut state)
184        }
185
186        fn box_eq(&self, other: &Box<dyn Task<$domain>>) -> bool {
187            other
188                .downcast_ref::<Self>()
189                .map_or(false, |other| self.eq(other))
190        }
191    };
192}