ecspanse icon indicating copy to clipboard operation
ecspanse copied to clipboard

Question about creating a server for each game session

Open henry-hz opened this issue 1 year ago • 3 comments

Dear @iacobson , thanks for sharing the amazing Ecspanse lib! In case we want to create a server for each game session, in order to optimize the latency in case we have many game sessions simultaneously, let me know if this approach [AI gen] would be fine:

Yes, if you want to have one server per group of players, you would need to modify the architecture to dynamically spawn Demo servers. Currently, the Demo module is started as a single instance in the supervision tree.

Here's a high-level approach to support multiple game instances:

1 Create a DynamicSupervisor to manage multiple game instances:

defmodule Demo.GameSupervisor do                                                                                                     
  use DynamicSupervisor                                                                                                              
                                                                                                                                     
  def start_link(_) do                                                                                                               
    DynamicSupervisor.start_link(__MODULE__, nil, name: __MODULE__)                                                                  
  end                                                                                                                                
                                                                                                                                     
  def init(_) do                                                                                                                     
    DynamicSupervisor.init(strategy: :one_for_one)                                                                                   
  end                                                                                                                                
                                                                                                                                     
  def start_game(game_id) do                                                                                                         
    # Each game would need its own unique name                                                                                       
    child_spec = %{                                                                                                                  
      id: Demo,                                                                                                                      
      start: {Demo, :start_link, [game_id]},                                                                                         
      restart: :transient                                                                                                            
    }                                                                                                                                
    DynamicSupervisor.start_child(__MODULE__, child_spec)                                                                            
  end                                                                                                                                
end                                                                                                                                  

2 Modify the application supervision tree:

 defmodule Demo.Application do                                                                                                        
   use Application                                                                                                                    
                                                                                                                                      
   @impl true                                                                                                                         
   def start(_type, _args) do                                                                                                         
     children = [                                                                                                                     
       Demo.GameSupervisor                                                                                                            
       # Other supervisors/workers...                                                                                                 
     ]                                                                                                                                
                                                                                                                                      
     opts = [strategy: :one_for_one, name: Demo.Supervisor]                                                                           
     Supervisor.start_link(children, opts)                                                                                            
   end                                                                                                                                
 end                                                                                                                                  

3 Modify the Demo module to accept a game_id and use it for naming:

defmodule Demo do                                                                                                                    
  use Ecspanse                                                                                                                       
                                                                                                                                     
  def start_link(game_id) do                                                                                                         
    Ecspanse.start_link(__MODULE__, game_id, name: via_tuple(game_id))                                                               
  end                                                                                                                                
                                                                                                                                     
  # Use Registry to track game instances                                                                                             
  defp via_tuple(game_id) do                                                                                                         
    {:via, Registry, {Demo.GameRegistry, game_id}}                                                                                   
  end                                                                                                                                
                                                                                                                                     
  # Rest of the module...                                                                                                            
end                                                                                                                                  

4 Then you could start new game instances like:

# Start a new game for group "group_1"                                                                                               
{:ok, pid} = Demo.GameSupervisor.start_game("group_1")                                                                               
                                                                                                                                     
# Start another game for group "group_2"                                                                                             
{:ok, pid2} = Demo.GameSupervisor.start_game("group_2")                                                                              

You would also need to:

1 Add a Registry to track the game instances
2 Modify your game logic to scope operations to specific game instances
3 Consider how to clean up/stop game instances when they're no longer needed
4 Handle game state persistence if needed
5 Consider how to route player actions to the correct game instance

henry-hz avatar Nov 25 '24 11:11 henry-hz

Hi @henry-hz !

First of all thanks for trying the Ecspanse library!

Regarding your question, the short answer, unfortunately, is that Ecspanse does not support multiple ECS servers. More users asked about it, but it was a decision I had to take in the early stages of the project.

The main reason is that Ecspanse needs to communicate with some outside UI (eg Liveview) or some API to produce any effects. Eg, display your game, or interact with your game as a server. In order to have multiple servers, you would need to know with every external request (event, query, etc) the name or the PID of the Ecspanse instance you are calling. I tried that, and it was a very "unfriendly" programming experience.

With a single named instance this is not needed anymore. The server is aware of its name and I can simply write the queries and events from outside the library context, making it much easier to deal with.

Please check this question: https://elixirforum.com/t/ecspanse-an-entity-component-system-framework-for-elixir/57650/41?u=iacobson and this answer https://elixirforum.com/t/ecspanse-an-entity-component-system-framework-for-elixir/57650/42?u=iacobson on the elixir forum more details, but also for some alternative to your question.

iacobson avatar Nov 26 '24 17:11 iacobson

Thanks @iacobson for your feedback and appreciation! in fact, the solution you wrote in the forum

player_entities =  Ecspanse.Query.list_children(room_entity)
## If the room has also other type of children
player_entities = Ecspanse.Query.select({Ecspanse.Entity}, 
  with: [MyGame.Comp.Player],
  for_children_of: [room_entity])
|> Ecspanse.Query.stream()
|> Enum.map( fn {player_entity} -> player_entity end )

can be quite good, we will probably have around 200 rooms for playing cards, so in a 5 FPS, we can still have a good performance without adding the multi-server complexity. I was wandering on a model to measure the latency performance based on the FPS , query complexitys and event frequency interactions with Ecspanse. Let me know if you have any tip :)

henry-hz avatar Dec 01 '24 08:12 henry-hz

Glad if it helps!

I was wandering on a model to measure the latency performance based on the FPS , query complexitys and event frequency interactions with Ecspanse.

I agree that some kind of performance/monitoring system would be cool. But I suggest it as a plugin to the library. Most Like what the ECSx library does: https://github.com/ecsx-framework/ecsx_live_dashboard

The creator of Ecspanse state machine library, was also investigating this: https://github.com/ketupia/ecspanse_live_dashboard

As discussed with him, I would be happy to add some monitoring/performace API (functions) to the library (limited to the free time I have :) ), to provide the base for anybody wanting to build a dashboard on top. Or also very happy to accept PRs for such API. Probably many things such as a system duration, how many times an event is triggered, etc, can be exposed with telemetry. But I personally did not look into it at all.

iacobson avatar Dec 05 '24 09:12 iacobson