Experimental: Modeling entity relations

has_many, has_one, belongs_to...

Experimental: Modeling entity relations

has_many, has_one, belongs_to

I've added an experimental ECS::EntityRelations extension to the ECS to make it easier to model relationships between entities. It's usually best to avoid that pattern in gamedev, so the design of the framework will continue to discourage overusing it and turning your game into some kind of Rails application... but I've found that certain kinds of problems in games just cannot be reasonably modeled any other way. The exact API will probably change before it's settled, but for now we have this:

The EntityBuilder DSL now includes these three new methods: belongs_to, has_one, and has_many. Entities connected in this way will be loosely associated by their entity ID using a BelongsTo({ relation: :entity, id: nil }) component under the hood (it is automatically added to the entity on that side of the relationship).

You can name your relations anything you want - they do not have to match the name of any class. As long as the value of belongs_to[:id] on an association matches the ID of your entity, you can create as many virtual has_one and has_many relations as you like with different sets of filters, and the result set will include whatever mix of entities those filters match.

Example
components do
	Person({ name: "Unknown" })
	Score({ points: 0 })
end

Player = entity do
	belongs_to :team
	Person()
end

Goal = entity do
	belongs_to :team
	Score()
end

Team = entity do
	has_many :players, filter: [Person]
	has_one :goal, filter: [Score]
end

world :example_game do
	entity Player, name: "Alice", as: :alice
	entity Player, name: "Bob", as: :bob
	entity Team, as :nerds

	systems AssignTeams, UpdateScore
end

AssignTeams = system do
	filter Person
	tick do |args|
		entities.each do |player|
			player.team = world.nerds if player.team.nil?
		end
	end
end

UpdateScore = system do
	tick do |args|
		world.teams.each do |team|
			if team.goal.nil?
				goal = Goal.new({ score: 0 })
				goal.team = team
				world.entities << [goal]
			end
		end
	end
end

nerds = world.nerds

nerds.players #> [Player<id: 1, Alice>, Player<id: 2, Bob>]
world.alice.team #=> Team<id: 3, players: [Alice, Bob], goal: Goal<id: 4, score: 0>
nerds.goal #=> Goal<id: 4, score: 0, team: Team<id: 3, players: [Alice, Bob]>