Closed brentjanderson closed 7 years ago
As I have reflected on this, the following points have been made pretty clear:
Figuring out how to handle those updates is the tricky part. Frankly, revisiting state as a whole from the perspective of video games wouldn't be a bad thing to do, however for the sake of moving forward, the above should be a good starting off point.
http://jaysoo.ca/2016/01/03/managing-processes-in-redux-using-sagas/
This link has some interesting comments about side effects on just a client-side basis. Each client, and actors on the server are event generators, thus giving us headaches about all of this in the first place.
State needs to be managed effectively in order to achieve the following objectives:
Freeze/Thaw vs Cloning
There are two approaches to creating a flight. One is to have a fully serialized flight state that is used to build the runtime of the flight (OTP Supervisor tree). This flight's state can then be serialized and stored back in the database as a new snapshot of a flight. This approach is similar to how Flint tackled this problem - it would simply scan all documents for the right simulator_id and it would build everything it needed for runtime based on that factor.
An alternative approach would be to have a mechanism for keeping track of flight state, and another mechanism for generating a flight in the first place. I'll call this "cloning". Essentially, a mission template would exist that describes the initial state of the flight (e.g. the number of simulators, their configuration, baseline sensor contacts for the universe, etc.). This initial state would serve as the foundation for the actual runtime events of the flight itself.
If Freeze/Thaw is sufficient, that's great. The biggest issue I see there is in the case of running the same "simulator" in multiple instances (e.g. online games). Due to primary keys and uniqueness, when "thawing" flight state, it would be necessary for the flight to not exist as an instantiated, running flight. Otherwise you'd see data overwritten and the flights would effectively merge in weird ways.
Perhaps, to synthesize all of this into a single workable solution, you need to have the following patterns established:
This is a bit of a brain dump, and there's a lot going on here, so to simplify, the following may be a tenable way forward:
Server side state management
Client-side state management
A naive implementation could be that the stream of events for a given store would stream through to the client and be played directly into Redux. Client-side mutations could be optimistic: When a client side event is dispatched, it is applied tentatively (assuming success), the event is sent to the server, the server approves or denies the event, and the true result based on server-side computation is then pushed back to the client. This is very similar to Meteor's approach. The downside is figuring out how to not push all the data to all the clients - there needs to be a subscription mechanism of some kind. Apollo should be trying to solve this problem in some way, but so far it's not fast enough based on your past experience.
Another approach would be to declare specific paths in the data structure of each store to watch for changes, and then react when those changes occur.
This is admittedly the part that is toughest for me to figure out at the moment, and will likely take some further thought.
Entity Component System
Something that would flip all of this around quite a bit is if we were to look at applying principles from https://en.wikipedia.org/wiki/Entity_component_system - Essentially everything is an "entity" in the simulation, and each entity can have a "component" attached to it. A "system" is a loop that scoops up each component during each tick of the game cycle, makes adjustments based on current input states, and then sets those values as a result. The advantages to this approach would be that it's already successful as a strategy in other games. The disadvantage is that we've never done it before. It would probably throw all this other stuff out the window, and I'm not advocating for it, but it's worth looking at to see how others are solving this problem. It would simplify a lot of how the data is managed. Note that each entity would not be a process, but each "system" would likely be an elixir process. I'm not sure how subscribing to a given entity would work, but it could work out alright.
Latency compensation
One thing that would make a lot of this better would be some kind of latency compensation. At its most basic form, I would expect two pieces - one would be "key frames" that would define the current state at a given moment in snapshots, to enforce client integrity, and another would be optimistically assuming certain conditions in the simulation. In other words, when a sensors contact is dragged to a new location at a given speed, only one event is emitted: "The contact is moving at this speed to this new place". The sever and the client would start animating the position of the contact based on this data, but the server would periodically send out "the client is at x,y,z location" so that there is still an authoritative answer about positioning. This periodic update would not have to be at 60 fps, though, so you get efficient networking coupled with realtime simulation.