Skip to content
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

Merged
merged 18 commits into from
Aug 4, 2024
Merged

Add Context - resolves #1 #4

merged 18 commits into from
Aug 4, 2024

Conversation

jasonasher
Copy link
Collaborator

This imports the code from eosim for Context.

@jasonasher jasonasher linked an issue Jul 19, 2024 that may be closed by this pull request
src/context.rs Outdated Show resolved Hide resolved
src/context.rs Outdated Show resolved Hide resolved
src/context.rs Outdated Show resolved Hide resolved
src/context.rs Outdated Show resolved Hide resolved
src/context.rs Outdated
callbacks_to_return
}

pub fn release_event<E: Copy + 'static>(&mut self, event: E) {
Copy link
Collaborator

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

Copy link
Collaborator Author

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 Show resolved Hide resolved
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 {
Copy link
Collaborator

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?

Copy link
Collaborator

@k88hudson-cfa k88hudson-cfa left a 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

src/context.rs Outdated
pub use define_plugin;

pub struct PlanId {
pub id: u64,
Copy link
Collaborator Author

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 {
Copy link
Collaborator

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 {
Copy link
Collaborator

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;
Copy link
Collaborator

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 {
Copy link
Collaborator

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")]
Copy link
Collaborator

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);
Copy link
Collaborator

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.

Copy link
Collaborator Author

@jasonasher jasonasher Jul 31, 2024

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.

src/plan.rs Outdated
};

pub struct Id {
id: usize,
Copy link
Collaborator

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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub struct Record {
struct Record {

I would also consider calling this QueueEntry or Entry

src/plan.rs Outdated
Comment on lines 32 to 38
if time_ordering == Ordering::Equal {
// Break time ties in order of plan id
self.id.cmp(&other.id).reverse()
} else {
time_ordering
}
}
Copy link
Collaborator

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:

Suggested change
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");
Copy link
Collaborator

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::{
Copy link
Collaborator

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) {
Copy link
Collaborator

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) => {
Copy link
Collaborator

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

Copy link
Collaborator

@ekr-cfa ekr-cfa left a 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
Comment on lines 30 to 31
/// The simulation also has an immediate callback queue to allow for the
/// accumulation of side effects of mutations by the current controlling module.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// 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> {
Copy link
Collaborator

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?

Copy link
Collaborator Author

@jasonasher jasonasher Aug 3, 2024

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 {
Copy link
Collaborator

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()

Copy link
Collaborator Author

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);
Copy link
Collaborator

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;
Copy link
Collaborator

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.

Copy link
Collaborator Author

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.

Copy link
Collaborator

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![]);
Copy link
Collaborator

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.

Copy link
Collaborator Author

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.
Copy link
Collaborator

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.

Copy link
Collaborator Author

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>`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// 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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// 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());
Copy link
Collaborator

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.

src/plan.rs Outdated
assert_eq!(next_plan.time, 1.0);
assert_eq!(next_plan.data, 1);

plan_queue.add_plan(3.0, 3);
Copy link
Collaborator

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.

Copy link
Collaborator

@ekr-cfa ekr-cfa left a 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.

Copy link
Collaborator

@k88hudson-cfa k88hudson-cfa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚢 it

@jasonasher jasonasher merged commit a8c4e9d into main Aug 4, 2024
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add Context
5 participants