Message release subscription mechanism in ECS

We found that the concept of ECS is born in the game area when practicing the ECS framework. The application of traditional human machine interaction is a response type: an external request triggers a series of business operations.

If you put the game business into the response framework, you will find that you have to trigger the time to trigger, the business response is Timer. However, in this case, Timer has almost no status, and the response to a single Timer is not possible: it is itself itered throughout the game world.

In this case, the response framework is very inefficient.

However, if the framework is completely cyclical, the processing of external input events is far less flexible as the response framework.

If you just simply operate input, such as a handle, we can place the status of each button of each frame, then SYSTEM is moving these status as a single case in a single case in the world when it is overlap. . But more complex inputs are not so good.

When we practiced the ECS framework, we would like to maintain the pureness of the entire model, deliberately avoiding things in the traditional response framework, just adding a message queue entered by an external event. For events generated within the frame, such as the birth, destruction of the component; the secondary effect of the state of the internal object is initiated; the events thrown by the physical system, the like, all convert to a state change. These states are processed by each frame of the corresponding SYSTEM.

I have recently I think that deliberately avoiding an event system in the ECS framework. The game service itself is also a mixture: mixed the cyclical world state iterative mode and response service processing mode.

So I am determined to add a complete message publishing subscription module to the ECS framework.

Since our framework is written in Lua, such a module can be made more flexible than the similar framework of C / C ++. Our messages are a key / value bearer, such as: {type “new”, EID 42} is a message, indicating that an ID of 42 is created. The mouse message may be {type “mouse”, action “move”, x 100, y 200}.

All messages are published in World: Pub (Message).

Any SYSTEM can subscribe to the Message that meets a class of Pattern through Mailbox World: Sub (Pattern). This pattern is also a key / value tuple, such as {type “new”} means all new types of messages; {type “mouse”} can follow all mouse messages, {type “mouse”, action “move”} It can be accurate to the movement of the mouse, {EID 42} You can focus on all things happened to Entity (usually log).

Any System can enumerate all messages in the mailbox via for MSG in mailbox: Each () DO, and empty the mailbox.

Take a look behind the implementation:

The highest time complexity is in the Pub message, in the current implementation, the moment of PUB has distributed the message to all Sub’s Mailbox, usually N Mailbox cares this message. Each Mailbox is a queue, so iteration is very simple.

Our message match is very flexible, the rules are very simple: every key / value in patter is a filter condition, only the message satisfies all the conditions, will be delivered to the corresponding mailbox. If it is not optimized, the time complexity of the PUB is O (n * m), n is the length of the message, and m is the amount of Mailbox in the system.

But optimization is also relatively easy. When SUB, establish an index cache, and use the space to change time.

For example: In a SUB Pattern, a Type “new” condition appears, then create a Type: New index table, put all the Mailbox that meet this condition in a collection (candidate set); Mailbox that meets this condition is also placed in a collection (exclusion set). Among them, the condition refers to the Pattern or Pattern doesn’t have TYPE in Pattern. Do not satisfy the condition, there is Type this key in Pattern but value is not “new”.

In this way, when a message is a message, according to one of the messages, it will be able to quickly determine, this condition can eliminate which Mailbox, and which Mailbox is a potential candidate. After traversing the message, the mailbox still staying in the list of candidates is screening it. Because the final candidate set will be reduced because each time is processed. In order to reduce the number of comparisons, you can sort the size of the candidate set corresponding to all conditions in the message, first filter the candidate set smaller conditions,

In this way, although the process is still o (n * m), M is the length of the shortest candidate, not all of the Milbox, Most of which correspond to 0 (no one cares).

If you actually use, the filter criteria can cause the Cache index set to account for too much memory. I think you can do some mergers with the meter meter mechanism, or some conditions do not generate cache, but degraded into traversal. Check.

In addition, for commonly used conditional combinations, a combined index cache can also be generated. Of course, these are where they can optimize.