I'm trying to provide "views" of non-owned structs to separate components of a system.
Assume a set of traits with distinct methods: Drawable, Modifiable and a number of structs which implement at least one of the traits - SimpleBox, Panel, Expression.
Different components of the system will need to frequently access sequences of these objects, using methods of specific traits; consider a DrawingManager or a ModifyManager:
struct DrawingManager {
items: Vec<Weak<Drawable>>,
}
struct ModifyManager {
items: Vec<Weak<Modifiable>>
}
While a single object may be referenced in both managers, assume that there is a separate single owner of all structs:
struct ObjectManager {
boxes: Vec<Rc<Box>>,
panels: Vec<Rc<Panel>>,
expressions: Vec<Rc<Expression>>,
}
Ideally, it would be useful to be able to manage deleting structs from one place - i.e simply removing it from the ObjectManager being enough to invalidate references in all other components (hence the use of Weak).
Is there a way of doing this?
Is this the correct way to achieve this?
Is there a more idiomatic way of implementing this functionality?
The system contains several traits, so making a single trait using methods of all the other traits seems like a bad idea. Several traits have more than one method, so replacing them with closures is not possible.
What I have tried
As one object may produce one or more Rc<Trait>, we might envision implementing this with a HashMap<ID, Vec<Rc<Any>>> whereby we make each struct have a unique ID, which maps to a list of all Rc that have been made for it.
When we want to remove an object, we remove it from the corresponding list, and remove the entry in the hashmap, invalidating all Weak references.
However, implementing this fails, as to insert into the HashMap, one must upcast a Rc<Trait> -> Rc<Any>, only to downcast it later.
I'm not sure if this is the idiomatic way of doing this, but I've since developed a crate providing this functionality - dependent_view.
Using the crate, the initial problem can be solved by using DependentRc instead of plain Rc's:
struct ObjectManager {
boxes: Vec<DependentRc<Box>>,
panels: Vec<DependentRc<Panel>>,
expressions: Vec<DependentRc<Expression>>
}
let object_manager : ObjectManager = ObjectManager::new();
Then using macros provided by the crate, we can obtain Weak<> references to these structs:
let box_view : Weak<Drawable> = to_view!(object_manager.boxes[0]);
let panel_view : Weak<Drawable> = to_view!(object_manager.panels[0]);
let expression_view : Weak<Drawable> = to_view!(object_manager.expressions[0]);
With this, dropping the corresponding DependentRc<> will invalidate all Weak<> references that have been made of it.
Related
So I have this component called BulletSpawner which spawns bullets every once in a while. I want this component to be as versatile as possible for my Danmaku game so that I don't have to change it again later. So it has fields like a TrajectoryBuilder for the trajectory of the bullets. And I wanted to include a field to allow inserting components on the bullet. That way, I could have multiple BulletSpawner spawning bullet with different shapes, sprites, etc... track the bullets a specific BulletSpawner had spawned.
I first tried writing the field to_insert: Arc<dyn Bundle> but it messaged me that 'Bundle' couldn't be made into an object. So I tried to use a vector of components to_insert: Vec<Arc<Component<Storage=Table>>> and that way it accepted the field. but now I have issues writing the snippet of code to insert the components
for component: &Arc<dyn Component<Storage=Table>> in bullet_spawner.to_insert.iter() {
new_bullet.insert(*Arc::borrow(component));
}
but now it's telling me that type annotation is needed, as it cannot infer the type for the type parameter 'impl Bundle' of the associated function insert.
These methods however feel like hacking so I would prefer an 'official' way.
I believe that what you have won't work in Bevy due to how it uses types for Components. When you store dyn Component there isn't a way back to the solid type that Bevy needs to work out which table to store it in.
Instead, you could change the design a little for the BulletSpawner so that it owns a function instead of instantiated components. e.g.
#[derive(Component)]
struct BulletSpawner {
// e.g. bundle: Bullet,
bundle_fn: Box<dyn Fn(&Commands) + Send + Sync>,
}
impl BulletSpawner {
fn create_bullet(&self, cmds: &Commands) {
(self.bundle_fn)(cmds);
}
}
fn spawner_startup_system(cmds: Commands) {
// Add new "spawner"
cmds.spawn(BulletSpawner {
bundle_fn: Box::new(|cmds| {
cmds.spawn(FastBullet {});
}),
});
}
That said, this still feels a little off. Splitting the concept of the BulletSpawner into multiple Component's might make for a more understandable structure, e.g. FastBulletSpawner.
C# 9 has a new feature. A record type. The record is just a class, but with a bunch of automatically created functions and properties. But basically the idea (as I undstand it) was, a class that behaves like structs, for things like copying, coimparison with Equals, immutibility and so on.
Also with the record type was a new feature with the keyword "with". To create a copy of a record, you can write something like that: var copy = original with { Property = new_value, };
Now I wondered, if records were designt to behave like structs (but are classes). Why doesn't the new "with" keyword works also with structs. I mean, as far as I can tell, structs have all features, that are necessary for this feature. Like they are copied by value.
Instead to use similar features for structs, I have to write a copy constructor and can then write: var copy = new StructType(original) { Property = new_value, };
Short answer:
That's how the feature was designed.
Long answer:
The compiler creates a synthesized clone method with a reserved name <Clone>$, when you use with keyword, the compiler calls this clone method, and then modifies whatever properties you want to modify.
structs or classes doesn't have a synthesized clone method. Hence, with can't be used with them.
You may want to write a language proposal to extend the usage of with keyword.
Edit:
Currently, there is a proposal for allowing record structs. See Proposal: record structs for more information. This is what you may want.
I really don't like monolithic implementations of the functions for a class (in C++ speak). In that language I can split things up as I like; in Rust there are strict rules about what goes in what files.
I have about 2000 lines (no comments/ docs) of impl for a struct. Logically they can be broken up into different sets; functions for managing aspect A, functions for managing aspect B, ... They all noodle on the struct's data big time, so chopping the struct up further wont help.
I saw in one answer that you can have
// in thing.rs
struct Thing{
.......
}
impl Thing{
fn1
fn2
}
// in more_thing.rs
use crate::thing::*;
impl Thing{
fn3,
fn4
}
// in lib.rs
mod thing;
mod more_thing;
This works, almost (I was surprised it worked at all). Its a kind of half way house. The problem is that for the methods in more_thing.rs I have to declare the fields of Thing all pub. Which is doable but not great. Are there any other options?
I know I can limit the pub scope but still it blows encapsulation.
All non-pub items in a module are still visible in its submodules. Just make more_thing a submodule of thing instead of a sibling. You can do this by putting it in a directory named thing, and putting the mod declaration inside thing.rs:
// thing.rs (or thing/mod.rs; see below)
pub struct Thing {
field: i32,
}
// Note the lack of `pub`: `more` is only an implementation detail
mod more;
// thing/more.rs
use super::Thing;
impl Thing {
// Although it is defined in a non-`pub` module, this method will be visible anywhere
// `Thing` is because it is marked `pub` and is a member of `Thing`. You can use
// `pub(crate)` or `pub(super)` instead to get different levels of visibility, or
// leave it private and it will only be available in the current module (thing::more)
pub fn field(&self) -> i32 {
// because more is a submodule of thing, non-`pub` members are visible here.
self.field
}
}
If you wish to keep all the Thing-related files in the thing directory, you can rename thing.rs to the special filename thing/mod.rs and it will work in exactly the same way.
All methods are private by default in Rust, which means they are only accessible in the scope of their containing module (which is never larger than a file), in this case thing.rs. thing.rs is just as remote to more_thing.rs as any libraries and external code; it may as well be a separate crate. In essence, you are trying to declare a private method on an external item, which of course fails.
However, this can be a bit confusing because when it comes to orphan rules, you can always implement traits for any item in the same crate, not just the same module. This is because trait implementations are always public (as long as you can access both the trait and the item implementing it; the actual orphan rules are a bit more complicated but this is the basic idea).
In essence: trait implementations are public, but method implementations are private by default. In order to implement public things you need at least public access, and in order to implement private things you need private access.
Instead, one solution is to simply declare a function that takes your item as an argument. For example, in more_thing.rs:
use super::thing::Thing;
fn foo(thing: &Thing) {
// ...
}
Why not put impl blocks in the same file?
in thing.rs
struct Thing {
.......
}
impl Thing {
fn1
fn2
}
impl Thing {
fn3
fn4
}
The methods are splited though there is still a large file.
I have two modules, MyCore and Special. MyCore has a public getter:
pub fn get_core_account() -> Option<T::AccountId>
Which gets an accountId. If I call this from Special in a simple manner:
let core_account = MyCore::get_core_account();
then rustc complains that it can't infer the type, which is odd because the public setters don't need further info. So I make the type explicit
let core_account: Option<T::AccountId> = MyCore::get_core_account();
But this triggers demands for type specifiers on the call, so we elaborate:
let core_account: Option<T::AccountId> = MyCore::<T>::get_core_account();
At which point rust complains that it can't find get_core_account, because Special doesn't have a restriction to implement MyCore::Trait. But I don't want Special to implement MyCore::Trait! I want to call a getter!
Perhaps I'm missing something regarding the use of T here - in theory, Special is a trait templated over some T, which we can implement using a Test class if we implement the required types.
Does Special really need to implement MyCore::Trait?
What you want to do is possible, yet you are probably doing something wrong which is not clearly demonstrated in the question. Would be very helpful if you post the Trait definitions of both modules.
Does Special really need to implement MyCore::Trait?
No. You might have to make it a trait bound though.
Basically, you have two options in such cases:
If your Core is something that all other modules will depend on, similar to frame-system, you can explicitly depend on it. Note that this path should be taken with care and you don't want to create too big of a monolithic software with this mindset. Nonetheless, in that case, you can just do:
// in special.rs
// This is needed because then we can pass `T` into `mycore::Module<_>`.
pub trait Trait: mucore::Trait {
...
}
// and then later on you can call:
<mycore::Module<T>>:: get_core_account()
This seems like the approach that you wanted to take.
Alternatively, you can bind the two modules together without this explicit dependency. This is useful for when you want your Special to receive the getter eventually, but you don't really care who provides it. Or in other words, if multiple candidates can provide this getter and you want to be generic over it.
For this, you first need a common trait definition.
// in some common dependency
trait CoreGetterProvider<AccountId> {
fn getter() -> Option<AccountId>;
}
and your Special would express:
pub trait Trait {
// .. other stuff. Assuming that it has a common AccountId with Core.
type Getter: CoreGetterProvider<Self::AccountId>
}
And your Core implements it
// in Core.rs
impl<T: Trait> CoreGetterProvider<T::AccountId> for Module<T> { ... }
Finally, when building the runtime, you can pass the Core module to Special.
impl core::Trait for Runtime { ... }
impl special::Trait for Runtime {
type Getter = core::Module<Runtime> // or just `Core`. construct_runtime! creates this type alias.
}
We have a struct with a LinkedList:
struct XPipeline {
handlers: LinkedList<XHandler>,
}
XPipeline is the owner of all XHandler objects and can access and modify them.
We already have the list of handlers; now we need that each handler could refer to its neighbors in the list. Namely, each handler's method could refer to handler's neighbors, modify them and invoke their methods.
My first thoughts were like this: I provide each handler with prev and next fields that will refer to neighbors. By adding a new handler into the list, I initialize these fields with corresponding references. Now I can use these references within all handler's methods. (That would be easy-peasy in C++ with pointers).
The problem is: only one owner (i.e. with modification permission) is allowed. And that owner (of all handlers) is already a XPipeline object. How could I solve it? Maybe, by employing:
handlers: Rc<RefCell<LinkedList<XHandler>>>
But how exactly?
One of the strategies in Rust for multiple links in data structures is to use a Vec<T> as the backing storage and then index into it with usize "pointers".
Your case would look something like:
struct XPipeline {
head: usize,
storage: Vec<Node>,
}
struct Node {
handler: XHandler,
next: Option<usize>,
prev: Option<usize>,
}
The bookkeeping is very similar to the pointers you would use in C++.
Also have a look at this discussion on Reddit for ways to deal with ownership in graph-like structures.
I would also just look for crates that implement double linked lists, skip lists, graphs or similar and take inspiration from there.