- Hplus:
I've considered interfaces before but I wasn't sure how to implement them. Perhaps this is because I've always wanted yo work with singular instances of manager classes. Would your idea be to provide a per class interface for ai functions? Inheriting it or composing it? - Kylotan:
Thanks. I think I may well 'see what I come up with'! I am sorry that I continue to ask insatiable questions, but I would like to get my solution more right than wrong first time, after having already tried a few different things.
When serialising the game state, would it make sense to join all entity, team, global data into a single "state" type, or should I dedicate a type for each component?
I used to use a polling event system and it would collate all the parts of the system into one state packet, however here are the problems I see at the moment:
- Adding new system aspects would require modifying the state "event" rather than just adding logic to be invoked (which seems a little clunky, less adaptable)
- Single packet sizes become increasingly large with the game as it scales
- Data prioritisation techniques would be difficult to implement[1]
Firstly, because the event system is really comprised of an event with a poll, send and recv method, It seems almost logical that you would add a special event for each different state member. For these reasons, would I be correct (permissible) to assume that it may be better to remove the definition of a single "state" and instead treat the state as a collection of information that it comprises of? I think separating things out would make it less complex to add prioritisation (see below)
Prioritisation [1]
There are two forms of prioritisation I am referring to here:
- Event prioritisation - Which events must be sent when they are requested, and which can be pushed to the next available packet? I think that in most cases this system would simply determine the order of the events sent, and would rarely interfere with the sending of a packet until the state data as we would say that it is needed most network ticks. Here we could assign certain events a higher priority so that entity packets are sent before score packets.
- Entity prioritisation - Which entities must be sent first, such as vehicles, close players to receiving player. I'm not too sure how I would fit this in with the above [2]
I would like to implement prioritisation. My existing system is quite limited in how it transmits to each user (it sends the entire game state, which is fine for small numbers of players, but not at all scalable). The problem with entity prioritisation is as follows:
[2]Assuming that number 2 is essentially referring to the TRIBES and HALO entity priorities, it would essentially conflict with the event priority. An entity which was deemed low priority and low relevance to the user (such as a door opening on the other side of the map) would be given a higher priority in the packet queue than a score packet which would arguably be more relevant. This leads me to conclude that I shouldn't use event prioritisation, and I should determine a global priority. At first I thought that it would make sense to batch entities into one entity packet, allowing me to save space by sending the packet type once for n entities. In doing so, I couldn't use the event priority and the entity priority unless I did some weird heuristics which would add far too much complexity. As it is unlikely that all entities will have exactly the same priority, I think that I would be forced to have a dynamic event priority - one that can vary per-entity. Here are the problems with this idea, to my mind:
- I would now have to increase overhead to 1 byte per entity. I would have to adapt the current packeting system so that all packets destined for the same tick would be batched inside a container. (I already have thought about supporting this using specified depths of packets [3]
- It sort of moves away from having dedicated state packets to a fragmented event dump.
[3] Here is a rough idea for packet separating. I currently have the idea of a specified (limited recursion depth) packet. Each packet sends (by default) its size. Optional protocol headers can be specified. When the game starts, the network layer is initialised with a certain depth of packets, something like this:
self.packet = Packet(child=Packet(child=Packet()))
The depth there is 3 (upper, secondary, lower). There shouldn't be too much overhead. The structure based off my suggestion above is something like this:
<packet_upper>
size_with_header = short(n)
base_tick = int(8910)
<packet_middle>
size_with_header = short(n)
group_tick_delta = char(13)
<packet_lower>
size_with_header = short(n)
packet_type = char(0)
...
</packet_lower>
<packet_lower>
size_with_header = short(n)
packet_type = char(1)
...
</packet_lower>
</packet_middle>
</packet_upper>
- Here the upper container represents the outgoing queue. Its protocol adds a base tick, integer, which is the full tick the packet started being populated. This could probably be excluded because network ticks are unlikely to span more than 3 / 4 game ticks, (and even if they were more it still doesn't save many bytes) so this extra tier increases overhead. Forgetting about this detail for now.
- The middle layer is the queue populated for a given tick. This is needed, and all the packets within this layer were intended for that tick.
- The lower layer is the individual event/interface output on that tick. Unfortunately, it adds some overhead with using a short for the size, although I could perhaps limit packets to 255 bytes because most things (entities etc) will be sent as one event instance and the maximum entity size will be around 20 bytes or less [4]
[4] For players, approximately 12 bytes for the position (which could be limited to a short * 3, equating to 6 bytes), one byte per boolean bitmasks, a few extra bytes for other networked attributes.