View Source Common Caveats

The ECSx API has been carefully designed to avoid common Elixir pitfalls and encourage efficient architectural patterns in your application. However, there are still some opportunities for bad patterns to sneak in and destabilize performance. This guide will list known problems and how to avoid them.

database-queries

Database Queries

It is expected that most applications will use a database at some point or another. However, when dealing with ECSx.Systems, which run many times per second, most databases are too slow to keep up as the load grows. Each server tick has a deadline to finish its work, and if it falls behind, there will be lag, unstable performance, and eventually game crashes.

Therefore:

You should never query the database from within a system

Instead: use ECSx.Manager.setup/1 to read the necessary data from the database at startup, and create components with it. Components are stored in memory, allowing the quick reads and writes which are required for system logic.

Example:

defmodule MyApp.Manager do
  use ECSx.Manager

  alias MyApp.Components.Height
  alias MyApp.Components.XPosition
  alias MyApp.Components.YPosition
  alias MyApp.Trees
  alias MyApp.Trees.Tree

  setup do
    for %Tree{id: id, x: x, y: y, height: height} <- Trees.my_db_query() do
        XPosition.add(id, x)
        YPosition.add(id, y)
        Height.add(id, height)
    end
  end
  ...
end

Then when our systems need to work with the height or position of trees, we use the ECSx.Component API instead of querying the database again.

external-requests

External Requests

Database queries are just one example of a slow call which can hold up the game systems. Any other request to a service outside your application will likely be too slow to be used in system logic.

Instead:

  • If you only need to make the request once upon initialization, use ECSx.Manager.setup/1 as shown above
  • If the request is made once, but triggered by input to some client process, that process should make the request (or spawn a Task for it)
  • If the request should happen regularly, create a new GenServer (don't forget to add it to your app's supervision tree)
  • If you have more than one external request happening regularly, this is a good use case for the Oban library
  • Remember that client processes (including GenServers, LiveViews, and Oban) have read-only access to components, and must use ECSx.ClientEvents for writes

## search/1 Without Index

ECSx.Component.search/1 will scan all Components of the given type to find matches. This can be OK if the quantity of Components of that type is small, or if it is just a one-time search. But if you are searching every tick within a System, through a large list of Components, this can become a performance concern. The solution is to set the index: true option, which will drastically improve search performance.