-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Context - resolves #1 #4
Conversation
src/context.rs
Outdated
callbacks_to_return | ||
} | ||
|
||
pub fn release_event<E: Copy + 'static>(&mut self, event: E) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the event emitter stuff new? We may way to split this out
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pulled out in latest commit
src/context.rs
Outdated
pub fn get_next_timed_plan(&mut self) -> Option<TimedPlan> { | ||
loop { | ||
let next_timed_plan = self.queue.pop(); | ||
match next_timed_plan { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can .map be used here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you rebase on main (which should remove the precommit changes) and also split out the event emitter stuff for now? I think after discussing it would be good to merge that separately.
Will do a more thorough pass after that's done
324171f
to
b1620b3
Compare
src/context.rs
Outdated
pub use define_plugin; | ||
|
||
pub struct PlanId { | ||
pub id: u64, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this should not have pub visibility? People using Context should only work with PlanIds that are returned and not construct them directly.
src/context.rs
Outdated
|
||
use derivative::Derivative; | ||
|
||
pub trait Plugin: Any { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As per discussion let's call this DataPlugin
src/context.rs
Outdated
} | ||
|
||
#[macro_export] | ||
macro_rules! define_plugin { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is also has to get renamed (and other usages)
src/context.rs
Outdated
pub trait Plugin: Any { | ||
type DataContainer; | ||
|
||
fn get_data_container() -> Self::DataContainer; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would make more sense if it was called create_data_container()
src/context.rs
Outdated
} | ||
pub use define_plugin; | ||
|
||
pub struct PlanId { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you put all this plan stuff in a plan.rs
file?
src/context.rs
Outdated
pub struct TimedPlan { | ||
pub time: f64, | ||
plan_id: u64, | ||
#[derivative(PartialEq = "ignore", Debug = "ignore")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add a comment about why this is needed
src/context.rs
Outdated
} | ||
|
||
pub fn cancel_plan(&mut self, id: PlanId) { | ||
self.invalid_set.insert(id.id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we discussed live, this should be structured in some way that it will panic or at least warn if you try to cancel a plan that has already run. I think you don't want to be in a scenario where there's a bug in your logic where you do the cancel too late but it just sort of fails silently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, good point. I implemented this in one relatively easy way and added tests.
291f21a
to
f2875b3
Compare
src/plan.rs
Outdated
}; | ||
|
||
pub struct Id { | ||
id: usize, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this usize
? I would probably use u64
.
src/plan.rs
Outdated
} | ||
|
||
#[derive(PartialEq, Debug)] | ||
pub struct Record { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pub struct Record { | |
struct Record { |
I would also consider calling this QueueEntry
or Entry
src/plan.rs
Outdated
if time_ordering == Ordering::Equal { | ||
// Break time ties in order of plan id | ||
self.id.cmp(&other.id).reverse() | ||
} else { | ||
time_ordering | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would be cleaner as:
if time_ordering == Ordering::Equal { | |
// Break time ties in order of plan id | |
self.id.cmp(&other.id).reverse() | |
} else { | |
time_ordering | |
} | |
} | |
match time_ordering { | |
Ordering::Equal => self.id.cmp(&other.id).reverse(), | |
_ => time_ordering | |
} |
/// This function panics if you cancel a plan which has already | ||
/// been cancelled or executed. | ||
pub fn cancel_plan(&mut self, id: &Id) { | ||
self.data_map.remove(&id.id).expect("Plan does not exist"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would add a comment, as in:
Delete the plan from the map, but leave it in the queue. It will be skipped when we get to the deadline
.
@@ -0,0 +1,139 @@ | |||
use std::{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this could use a block comment at the top describing the architecture.
src/plan.rs
Outdated
loop { | ||
match self.queue.pop() { | ||
Some(plan_record) => { | ||
if let Some(data) = self.data_map.remove(&plan_record.id) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would also add a comment here to explain what is happening.
src/plan.rs
Outdated
pub fn get_next_plan(&mut self) -> Option<Plan<T>> { | ||
loop { | ||
match self.queue.pop() { | ||
Some(plan_record) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you take my advice above, I would call this entry
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is basically ready, except for my comment about get_data_container()
which I think requires either a change or an argument about why I'm wrong.
Either way, we should have a test that exercises calling get_data_container()
when one doesn't exist.
src/context.rs
Outdated
/// The simulation also has an immediate callback queue to allow for the | ||
/// accumulation of side effects of mutations by the current controlling module. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// The simulation also has an immediate callback queue to allow for the | |
/// accumulation of side effects of mutations by the current controlling module. | |
/// The simulation also has a separate callback mechanism. Callbacks | |
/// fire before the next timed event (even if it is scheduled for the | |
/// current time. This allows modules to schedule actions for immediate | |
/// execution but outside of the current iteration of the event loop. |
/// | ||
/// Returns a reference to the data container if it exists or else `None` | ||
#[must_use] | ||
pub fn get_data_container<T: DataPlugin>(&self) -> Option<&T::DataContainer> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not so sure this is the right design choice. Why make readers deal with the possibility that this is empty rather than returning an empty container?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea is that we want this method to return a reference to the data container owned by the Context
if it exists. If it doesn't there isn't a way to return a reference as nothing would own the data container. Circumventing this would entail interior mutability which I think for now we want to avoid.
} | ||
} | ||
|
||
impl Default for Context { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I would put this up near new()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's idiomatic to follow the struct with its impl before doing other traits, so I don't think there is a way to move this up more and keep with that.
src/context.rs
Outdated
} | ||
} | ||
|
||
type Callback = dyn FnOnce(&mut Context); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I would put this earlier because we depend on it.
} | ||
}; | ||
} | ||
pub use define_data_plugin; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would either put this stuff earlier or alternately, in a different file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to define Context first as it was the main focus of this source file. It seemed natural to have the DataPlugin defined after that together with its construction macro.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there is a philosophical question here. In general, I think that things should appear before things that depend on them, or in some separate location and be imported in. In this case, neither is true. The question is whether it's possible to infer what DataPlugin does from the code that uses it, and I am skeptical of that.
mod tests { | ||
use super::*; | ||
|
||
define_data_plugin!(ComponentA, Vec<u32>, vec![]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are really thorough tests. This is approximately how I would do it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I worked hard to get those right.
//! * A disease progression manager that transitions infected people through | ||
//! stages of disease until recovery. | ||
//! * A transmission manager that models the process of an infected | ||
//! person trying to infect susceptible people in the population. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doc is fine, but I wouldn't put it in lib.rs. I would just put it in README.md or something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I put it here so it would show up in the generated Rust docs for the library. Will remove if you feel strongly.
src/plan.rs
Outdated
|
||
/// A priority queue that stores arbitrary data sorted by time | ||
/// | ||
/// Items of type T are stored in order by `f64` time and called `Plan<T>`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Items of type T are stored in order by `f64` time and called `Plan<T>`. | |
/// Items of type `T` are stored in order by `f64` time and called `Plan<T>`. |
src/plan.rs
Outdated
/// Items of type T are stored in order by `f64` time and called `Plan<T>`. | ||
/// When plans are created they are sequentially assigned an `Id` that is a | ||
/// wrapped `u64`. If two plans are scheduled for the same time the plan that is | ||
/// scheduled first (i.e. that has the lowest id) is placed earlier. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// scheduled first (i.e. that has the lowest id) is placed earlier. | |
/// scheduled first (i.e., that has the lowest id) is placed earlier. |
assert_eq!(next_plan.time, 3.0); | ||
assert_eq!(next_plan.data, 3); | ||
|
||
assert!(plan_queue.get_next_plan().is_none()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to see a test that demonstrates that you can add a plan once you started servicing the queue, not just all at once.
675915f
to
70a6bfb
Compare
src/plan.rs
Outdated
assert_eq!(next_plan.time, 1.0); | ||
assert_eq!(next_plan.data, 1); | ||
|
||
plan_queue.add_plan(3.0, 3); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would consider adding it with 1.5 to demonstrate that sort works properly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, but I added one comment below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚢 it
This imports the code from
eosim
for Context.