-
Notifications
You must be signed in to change notification settings - Fork 221
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
Some way to make a snapshot of part of the world #690
Comments
You can have multiple Dispatcher's for note. :-) A snapshot doesn't sound terribly efficient, but if you are wanting to keep, say, two worlds between logic and render then I'd probably do exactly that, have changes and updates on the logic send send across data to the render thread that the renderer needs to be able to render (and no more), crossbeam channels are useful for that in my tests, though abstracting it out more to be fully client/server internally might not be a bad idea as it would make setting up multiplayer more easy in the future. |
I don't think crossbeam channels are the appropriate thing here (because they can be set up to lose the newest, but not the oldest value, you need just one value and you don't need wake up the other side). But I'm sure there are appropriate synchronization mechanisms for that. So your proposal is to somehow take the data from one world, transfer them and inject them into another world. How would I do that? Somehow try to read the raw storage(s) + all the entities and plug them in into the other world? |
Same way you'd do it in multiplayer. Think of one side as the 'server' and the other side as the 'client'. The client only needs to know enough to represent the world visually, the server is what holds the actual processing data, but doesn't actually need to hold model information or so. A layer between them to pass data directly is the usual thing to do, you just pretend it's packet transfers over tcp/udp/whatever. A common pattern is to actually send packets between the 'client' and 'server' code in the same program first, then abstract it out to actually send memory later instead. |
Right, that's what I mostly wanted to avoid. I don't know if it's just my sense of engineering elegance, but having to do some kind of export to other format and then import somewhere else seems like needless work (both for me and the computer). For this to work, there needs to be some kind of mapping between entity IDs in the first and second world, going over all the data at least two more times… Then, you can either send the whole state each frame, even if most of it doesn't change, or you need to track and generate diffs. But then you can't just skip some if you don't keep up. My idea was more in the sense of this (omitting lifetimes and other letter soup): impl System for ExportSystem {
type SystemData = (Entities, ReadStorage<Position>, ReadStorage<Image>);
fn run(&mut self, data: SystemData) {
*global_storage.lock() = Arc::new(Export {
entities: data.0.clone(),
positions: data.1.clone(),
images: data.2.clone(),
});
}
} The other world would have something like this: let current_export = Arc::clone(global_storage.lock());
world.insert(current_export.entities.clone());
world.insert(current_export...);
draw_dispacter.dispatch(&mut world); For this to work, several things would be needed, though:
I believe both could be tweaked somehow (eg. having a If I'm not explaining myself clearly, I might try putting some kind of PoC together, but currently I don't have an idea how much work that would be. |
Doing this via mapping the events identically on all sides is generally known as the lockstep, or Deadman's simulation, and is also quite popular for certain types of game or keeping multiple simulations concurrently at different 'steps' for interpolation and synchronization. The nice bit about this is you only need to record the 'inputs' into the systems, as long as your systems are deterministic (so fake 'noise' randomness, not any normal random calls and so forth, careful float usage, etc...) then it will be identical output every time. As an ancient example the game Doom's replay files are tiny because it only need to record the level ID, the level seed, and the user inputs to work. :-)
You are actually getting a style very close to what I've been experimenting with in an ECS for a week now. ^.^
Making at least a pseudo-PoC for specs would be very useful and help flesh out the idea, I'm all for you looking in to it! |
That is all very nice, but I'm not really trying to do a multiplayer here 😇.
I've tried looking into it. However, my attempt at making the EntitiesRes type clonable or, to make it a different range than i32 or to identify what the behaviour of the type needs to satisfy hasn't been successful yet, mostly because it seems quite interlinked with non-trivial assumptions across all the bitsets and how it is used. I might get to it eventually and give it some more time, but it doesn't seem doable as 1-2 hour PoC :-(. Nevertheless, I wonder about one thing. If I need the entities at all. I'll try experimenting if a world without entities in it (or with entities not matching the actual components) behave in some sane way. |
Description
I'd like to have some way to get a lightweight snapshot of part of the world state for later use. I'm not entirely sure about the form which would be best, though (I'm not really talking about full saves).
Motivation
I have two things in mind.
First is, I'd like to separate the game logic update from rendering. Right now, the canonical usage of specs seems to be something like:
However, I'd like to be able to have one thread that does only the rendering while some other thread (or the whole threadpool) does the updates. That way, if rendering is slow, the game logic would still be able to keep up nevertheless, just some frames would be skipped. So I'd like to take a snapshot of all the entities that can be drawn and their positions after each logic-update, store this snapshot somewhere (eg. in a global Arc). The renderer thread would take the latest exported thread and render that.
Another use case I see is a game where communication would be slow. You'd see the events that happened far away only if a unit came from there, but it would only be able to show the state at the time it was there, not the current one. Such thing would need the units to „remember“ some older states (small parts of them, actually).
I'm thinking something in lines of having component storages based on the
im
crate might help with that, and being able to somehow take the snapshot and import it into another copy of the world… but I don't have concrete ideas. Or are there some other ways I'm overlooking?Drawbacks
If it was only adding few more storage types, possibly behind a feature flag, then it shouldn't be breaking.
Probably, but only when these snapshots would be used ‒ at least when used, such storages might need more memory and be slower.
Unresolved questions
Well, how it would actually look like specifically 😇
Please indicate here if you'd like to work on this ticket once it's been approved. Feel free to delete this section if not.
Yes, but I'd probably need some help/guidance with designing the API and approval first.
The text was updated successfully, but these errors were encountered: