class GameEcs::EntityStore

Attributes

entity_count[R]
id_to_comp[R]

Public Class Methods

new() click to toggle source
# File lib/game_ecs/entity_store.rb, line 7
def initialize
  clear!
end

Public Instance Methods

add_component(component:,id:) click to toggle source
# File lib/game_ecs/entity_store.rb, line 117
def add_component(component:,id:)
  if _iterating?
    _add_component_later component: component, id: id
  else
    _add_component component: component, id: id
  end
end
add_entity(*components) click to toggle source
# File lib/game_ecs/entity_store.rb, line 141
def add_entity(*components)
  id = generate_id
  if _iterating?
    _add_entity_later(id:id, components: components)
  else
    _add_entity(id: id, components: components)
  end
  id
end
clear!() click to toggle source
# File lib/game_ecs/entity_store.rb, line 27
def clear!
  @comp_to_id = {}
  @id_to_comp = {}
  @cache = {}
  @entity_count = 0

  @iterator_count = 0
  @ents_to_add_later = []
  @comps_to_add_later = []
  @comps_to_remove_later = []
  @ents_to_remove_later = []
  clear_cache!
end
clear_cache!() click to toggle source
# File lib/game_ecs/entity_store.rb, line 41
def clear_cache!
  @cache = {}
end
deep_clone() click to toggle source
# File lib/game_ecs/entity_store.rb, line 11
def deep_clone
  # NOTE! does not work for Hashes with default procs
  if _iterating?
    raise "AHH! EM is still iterating!!" 
  else
    _apply_updates
    clear_cache!
    em = Marshal.load( Marshal.dump(self) )
    em
  end
end
each_entity(*klasses, &blk) click to toggle source
# File lib/game_ecs/entity_store.rb, line 99
def each_entity(*klasses, &blk)
  ents = find(*klasses)
  if block_given?
    _iterating do
      ents.each &blk
    end
  end
  ents
end
find(*klasses)
Alias for: musts
find_by_id(id, *klasses) { |rec| ... } click to toggle source
# File lib/game_ecs/entity_store.rb, line 45
def find_by_id(id, *klasses)
  return nil unless @id_to_comp.key? id
  ent_record = @id_to_comp[id]
  components = ent_record.values_at(*klasses)
  rec = build_record(id, @id_to_comp[id], klasses) unless components.any?(&:nil?)
  if block_given?
    yield rec
  else
    rec
  end
end
first(*klasses) click to toggle source
# File lib/game_ecs/entity_store.rb, line 95
def first(*klasses)
  find(*klasses).first
end
musts(*klasses) click to toggle source
# File lib/game_ecs/entity_store.rb, line 57
def musts(*klasses)
  raise "specify at least one component" if klasses.empty?
  q = Q
  klasses.each{|k| q = q.must(k)}
  query(q)
end
Also aliased as: find
num_entities() click to toggle source
# File lib/game_ecs/entity_store.rb, line 23
def num_entities
  @id_to_comp.keys.size
end
query(q) click to toggle source
# File lib/game_ecs/entity_store.rb, line 65
def query(q)
  # TODO cache results as q with content based cache
  #   invalidate cache based on queried_comps
  cache_hit = @cache[q]
  return cache_hit if cache_hit

  queried_comps = q.components
  required_comps = q.required_components

  required_comps.each do |k|
    @comp_to_id[k] ||= Set.new
  end

  intersecting_ids = []
  unless required_comps.empty?
    id_collection = @comp_to_id.values_at(*required_comps)
    intersecting_ids = id_collection.sort_by(&:size).inject &:&
  end

  recs = intersecting_ids.
    select{|eid| q.matches?(eid, @id_to_comp[eid]) }.
    map do |eid|
      build_record eid, @id_to_comp[eid], queried_comps
    end
  result = QueryResultSet.new(records: recs, ids: recs.map(&:id))

  @cache[q] = result if q.cacheable?
  result
end
remove_component(klass:, id:) click to toggle source
# File lib/game_ecs/entity_store.rb, line 109
def remove_component(klass:, id:)
  if _iterating?
    _remove_component_later klass: klass, id: id
  else
    _remove_component klass: klass, id: id
  end
end
remove_entites(ids:) click to toggle source
# File lib/game_ecs/entity_store.rb, line 125
def remove_entites(ids:)
  if _iterating?
    _remove_entities_later(ids: ids)
  else
    _remove_entites(ids: ids)
  end
end
remove_entity(id:) click to toggle source
# File lib/game_ecs/entity_store.rb, line 133
def remove_entity(id:)
  if _iterating?
    _remove_entity_later(id: id)
  else
    _remove_entity(id: id)
  end
end

Private Instance Methods

_add_component(component:,id:) click to toggle source
# File lib/game_ecs/entity_store.rb, line 202
def _add_component(component:,id:)
  raise "Cannot add nil component" if component.nil?

  @comp_to_id[component.class] ||= Set.new
  @comp_to_id[component.class] << id
  @id_to_comp[id] ||= {}
  ent_record = @id_to_comp[id]
  klass = component.class

  raise "Cannot add component twice! #{component} -> #{id}" if ent_record.has_key? klass
  ent_record[klass] = component

  @cache.each do |q, results|
    # TODO make results a smart result set that knows about ids to avoid the linear scan
    # will musts vs maybes help here?
    comp_klasses = q.components
    if comp_klasses.include?(klass)
      if results.has_id?(id)
        results.add_component(id: id, component: component)
      else
        results << build_record(id, ent_record, comp_klasses) if q.matches?(id, ent_record)
      end
    end
  end
  nil
end
_add_component_later(component:,id:) click to toggle source
# File lib/game_ecs/entity_store.rb, line 167
def _add_component_later(component:,id:)
  @comps_to_add_later << {component: component, id: id}
end
_add_entity(id:, components:) click to toggle source
# File lib/game_ecs/entity_store.rb, line 273
def _add_entity(id:, components:)
  components.each do |comp|
    _add_component component: comp, id: id
  end
  id
end
_add_entity_later(id:,components:) click to toggle source
# File lib/game_ecs/entity_store.rb, line 152
def _add_entity_later(id:,components:)
  @ents_to_add_later << {components: components, id: id}
end
_apply_updates() click to toggle source
# File lib/game_ecs/entity_store.rb, line 171
def _apply_updates
  _remove_entites ids: @ents_to_remove_later
  @ents_to_remove_later.clear

  @comps_to_remove_later.each do |opts|
    _remove_component klass: opts[:klass], id: opts[:id]
  end
  @comps_to_remove_later.clear

  @comps_to_add_later.each do |opts|
    _add_component component: opts[:component], id: opts[:id]
  end
  @comps_to_add_later.clear

  @ents_to_add_later.each do |opts|
    _add_entity id: opts[:id], components: opts[:components]
  end
  @ents_to_add_later.clear
end
_iterating() { || ... } click to toggle source
# File lib/game_ecs/entity_store.rb, line 191
def _iterating
  @iterator_count += 1
  yield
  @iterator_count -= 1
  _apply_updates unless _iterating?
end
_iterating?() click to toggle source
# File lib/game_ecs/entity_store.rb, line 198
def _iterating?
  @iterator_count > 0
end
_remove_component(klass:, id:) click to toggle source
# File lib/game_ecs/entity_store.rb, line 229
def _remove_component(klass:, id:)
  @comp_to_id[klass] ||= Set.new
  @comp_to_id[klass].delete id
  @id_to_comp[id] ||= {}
  @id_to_comp[id].delete klass

  @cache.each do |q, results|
    comp_klasses = q.components
    if comp_klasses.include?(klass)
      results.delete(id: id) unless q.matches?(id, @id_to_comp[id])
    end
  end
  nil
end
_remove_component_later(klass:,id:) click to toggle source
# File lib/game_ecs/entity_store.rb, line 164
def _remove_component_later(klass:,id:)
  @comps_to_remove_later << {klass: klass, id: id}
end
_remove_entites(ids:) click to toggle source
# File lib/game_ecs/entity_store.rb, line 244
def _remove_entites(ids:)
  return if ids.empty?

  ids.each do |id|
    @id_to_comp.delete(id)
  end

  @comp_to_id.each do |_klass, ents|
    ents.delete_if{|ent_id| ids.include? ent_id}
  end

  @cache.each do |comp_klasses, results|
    results.delete ids: ids
  end
end
_remove_entities_later(ids:) click to toggle source
# File lib/game_ecs/entity_store.rb, line 155
def _remove_entities_later(ids:)
  ids.each do |id|
    @ents_to_remove_later << id
  end
end
_remove_entity(id:) click to toggle source
# File lib/game_ecs/entity_store.rb, line 260
def _remove_entity(id:)
  comp_map = @id_to_comp[id]
  if @id_to_comp.delete(id)
    ent_comps = comp_map.keys
    ent_comps.each do |klass|
      @comp_to_id[klass].delete id
    end
    @cache.each do |_query, results|
        results.delete id: id
    end
  end
end
_remove_entity_later(id:) click to toggle source
# File lib/game_ecs/entity_store.rb, line 160
def _remove_entity_later(id:)
  @ents_to_remove_later << id
end
build_record(*args) click to toggle source
# File lib/game_ecs/entity_store.rb, line 286
def build_record(*args)
  EntityQueryResult.new(*args)
end
generate_id() click to toggle source
# File lib/game_ecs/entity_store.rb, line 280
def generate_id
  @entity_count += 1
  @ent_counter ||= 0
  @ent_counter += 1
end