-
Notifications
You must be signed in to change notification settings - Fork 7
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
State #19
Comments
Ah I think this won't work because if the function names the types in that tuple then we are back to the same problem. I guess we could use Box instead like you said. |
I guess what's going through my head with this is we know the order of the nodes in the evaluation so it would be nice if we could just use that to in a stack of state. I think the only way I can imagine this working without any performance penalty is having some sort of buffer that basically emulates a programs stack. You could have a "stack" for every evaluation routine. We need some way to share memory across crates but the types that are only visible to their crate and opaque to all other crates. Like
|
Yeah I agree there needs to be some nice way of providing state to the node's expression that doesn't require a user trying to cast to a potentially incorrect type or something. Originally I was thinking of using some sort of map like
Push / Pull Evaluation function argumentIn order to get this list of node states into the function, we need to be able to pass in a type that won't require changing no matter how the graph is manipulated as we can't change this function signature at runtime if we need to call it within our application code. As a result, out best option will probably be to use something like Casting to the expected typeOnce we have this list of trait objects representing each node's state available in the function, we need to know what type to cast them to for each node. This means we need some way for a node to be able to indicate the type that it expects. Perhaps a node can both opt-in to being "stateful" and describe the state type it expects with a single, optional method on the fn state_type(&self) -> Option<syn::Type>; where the default implementation returns A simple impl Node for Counter {
fn state_type(&self) -> Option<syn::Type> {
let ty: syn::Type = syn::parse_quote! { u32 };
Some(ty)
}
fn expr(&self, _args: Vec<syn::Expr>) -> syn::Expr {
let expr: syn::Expr = syn::parse_quote! {{
let count = *state;
*state += 1;
count
}};
expr
}
// Other methods omitted...
} Simple ExampleHere's an annotated snippet of some hypothetical generated code for a simple graph that just has a #[no_mangle]
pub fn button_push_eval(_node_states: &mut [&mut Any]) {
let () = (); // Button expr does nothing, its only purpose is to support push evaluation.
let _node1_output0 = {
// The generated line where we cast from the trait object to the state type expected by the node.
// We use the index `0` as it's the first stateful node.
let state: &mut u32 = _node_states[0].downcast_mut::<u32>().expect("some err msg");
// The expression returned by the counter's `Node::expr` method.
{
let count = *state;
*state += 1;
count
}
};
let () = println!("{:?}", { _node1_output0 });
} We could probably provide some nicer type that wraps the @freesig what are your thoughts on this direction? I still like your original idea of somehow packing all the state into a single struct and putting it behind a trait object, as it means we would only have to do a single cast for all the node state for a single graph (rather than casting each individual node's state), but I'm not yet sure how or if we can do this in a way that allows the user to be able to actually create this generated struct 🤔 |
Yeh I see what you mean about the function signature not being able to change at runtime.
The fn eval(state: State) -> State {
// this is the first time we see a node so it gets position 0
// The [] operator handles the cast
let _node19_output0 = { 1 + state[0].downcast_mut::<Node19StateType>().expect("some err msg") ) };
// State is [1] for node3 because it's the second node we have seen
let _node3_output0 = if _node19_output0 {
// State is [2] for node5 because it's the third node we have seen
let _node5_output = {state[2].downcast_mut::<Node5StateType>().expect("some err msg") };
state[1].downcast_mut::<Node3StateType>().expect("some err msg") + _node5_output
} else {
// State is [3] for node22 because it's the fourth node we have seen
let _node22_output = {state[3].downcast_mut::<Node22StateType>().expect("some err msg") };
state[1].downcast_mut::<Node3StateType>().expect("some err msg") + _node22_output
}
} |
This allows for optionally specifying the full types of the inputs and outputs of a `Node` during implementation by allowing to specify a full, freestanding function, rather than only an expression. The function's arguments and return type will be parsed to produce the number of inputs and outputs for the node, where the number of arguments is the number of inputs, and the number of tuple arguments in the output is the number of outputs (1 if no tuple output type). Some of this may have to be re-written when addressing a few follow-up issues including nannou-org#29, nannou-org#19, nannou-org#21 and nannou-org#22, but I think it's helpful to break up progress into achievable steps! Closes nannou-org#27 and makes nannou-org#20 much more feasible.
This allows for optionally specifying the full types of the inputs and outputs of a `Node` during implementation by allowing to specify a full, freestanding function, rather than only an expression. The function's arguments and return type will be parsed to produce the number of inputs and outputs for the node, where the number of arguments is the number of inputs, and the number of tuple arguments in the output is the number of outputs (1 if no tuple output type). Some of this may have to be re-written when addressing a few follow-up issues including nannou-org#29, nannou-org#19, nannou-org#21 and nannou-org#22, but I think it's helpful to break up progress into achievable steps! Closes nannou-org#27 and makes nannou-org#20 much more feasible.
This adds rough support for node state that persists between calls to evaluation functions. Currently, node state is made available in evaluation functions via an added `&mut [&mut dyn std::any::Any]`. Eventually, this should be changed to a friendlier `node::States` type or something along these lines, however this will first require some way to make such a type available to the generated crate. This might mean splitting this type into its own crate so that it may be shared between both gantz, generated crates and user crates. For now, the `Any` slice only requires `std` and seems to work as a basic prototype. A simple counter.rs test has been added in which a small graph containing a counter node is composed, compiled, loaded, evaluated three times and the result is asserted at the end. I'm not sure if Rust's unstable ABI will begin causing more issues with larger types, but for now this seems to be working consistently OK with primitive types on Linux. This should make it possible to finish nannou-org#20. Closes nannou-org#19.
This adds rough support for node state that persists between calls to evaluation functions. Currently, node state is made available in evaluation functions via an added `&mut [&mut dyn std::any::Any]`. Eventually, this should be changed to a friendlier `node::States` type or something along these lines, however this will first require some way to make such a type available to the generated crate. This might mean splitting this type into its own crate so that it may be shared between both gantz, generated crates and user crates. For now, the `Any` slice only requires `std` and seems to work as a basic prototype. A simple counter.rs test has been added in which a small graph containing a counter node is composed, compiled, loaded, evaluated three times and the result is asserted at the end. I'm not sure if Rust's unstable ABI will begin causing more issues with larger types, but for now this seems to be working consistently OK with primitive types on Linux. This should make it possible to finish nannou-org#20. Closes nannou-org#19.
This adds rough support for node state that persists between calls to evaluation functions. Currently, node state is made available in evaluation functions via an added `&mut [&mut dyn std::any::Any]`. Eventually, this should be changed to a friendlier `node::States` type or something along these lines, however this will first require some way to make such a type available to the generated crate. This might mean splitting this type into its own crate so that it may be shared between both gantz, generated crates and user crates. For now, the `Any` slice only requires `std` and seems to work as a basic prototype. A simple counter.rs test has been added in which a small graph containing a counter node is composed, compiled, loaded, evaluated three times and the result is asserted at the end. I'm not sure if Rust's unstable ABI will begin causing more issues with larger types, but for now this seems to be working consistently OK with primitive types on Linux. This should make it possible to finish nannou-org#20. Closes nannou-org#19.
@mitchmindtree I have an idea for state. Does this work?
Then each Node that needs state could also impl
fn state(&self) -> syn::Expr
where the syn::Expr is the Node0State type etc.And this function could have a default impl of syn::Expr is
()
.Then we can access the state like
Alternatively state could be passed in as
&mut
but this might cause issues.This turns each eval function into something similar to nannou's
Model -> Model
Then we could make some helper functions for the node to easily get it's state out of the return value.
The text was updated successfully, but these errors were encountered: