Class: Chione::World
- Inherits:
-
Object
- Object
- Chione::World
- Extended by:
- MethodUtilities, Configurability, Loggability
- Defined in:
- lib/chione/world.rb
Overview
The main ECS container
Instance Attribute Summary collapse
-
#components_by_entity ⇒ Object
readonly
The Hash of Hashes of Components which have been added to an Entity, keyed by the Entity’s ID and the Component class.
-
#deferred_events ⇒ Object
readonly
The queue of events that have not yet been sent to subscribers.
-
#entities ⇒ Object
readonly
The Hash of all Entities in the World, keyed by ID.
-
#entities_by_component ⇒ Object
readonly
The Hash of Sets of Entities which have a particular component, keyed by Component class.
-
#main_thread ⇒ Object
readonly
The Thread object running the World’s IO reactor loop.
-
#managers ⇒ Object
readonly
The Hash of all Managers currently in the World, keyed by class.
-
#subscriptions ⇒ Object
readonly
The Hash of event subscription callbacks registered with the world, keyed by event pattern.
-
#systems ⇒ Object
readonly
The Hash of all Systems currently in the World, keyed by class.
-
#tick_count ⇒ Object
The number of times the event loop has executed.
-
#world_threads ⇒ Object
readonly
The ThreadGroup that contains all Threads managed by the World.
Instance Method Summary collapse
-
#add_component_to(entity, component, **init_values) ⇒ Object
(also: #add_component_for)
Add the specified
componentto the specifiedentity. -
#add_manager(manager_type, *args) ⇒ Object
Add an instance of the specified
manager_typeto the world and return it. -
#add_system(system_type, *args) ⇒ Object
Add an instance of the specified
system_typeto the world and return it. -
#call_subscription_callback(callback, event_name, payload) ⇒ Object
Call the specified
callbackwith the providedevent_nameandpayload, returningtrueif the callback executed without error. -
#call_subscription_callbacks(event_name, payload) ⇒ Object
Call the callbacks of any subscriptions matching the specified
event_namewith the givenpayload. -
#components_for(entity) ⇒ Object
Return a Hash of the Component instances associated with
entity, keyed by their class. -
#create_blank_entity ⇒ Object
Return a new Chione::Entity with no components for the receiving world.
-
#create_entity(archetype = nil) ⇒ Object
Return a new Chione::Entity for the receiving World, using the optional
archetypeto populate it with components if it’s specified. -
#defer_events ⇒ Object
Whether or not to queue published events instead of sending them to subscribers immediately.
-
#destroy_entity(entity) ⇒ Object
Destroy the specified entity and remove it from any registered systems/managers.
-
#entities_with(aspect) ⇒ Object
Return an Array of all entities that match the specified
aspect. -
#get_component_for(entity, component_class) ⇒ Object
Return the Component instance of the specified
component_classthat’s associated with the givenentity, if it has one. -
#has_component_for?(entity, component) ⇒ Boolean
Return
trueif the specifiedentityhas the givencomponent. -
#has_entity?(entity) ⇒ Boolean
Returns
trueif the world contains the specifiedentityor an entity withentityas the ID. -
#initialize ⇒ World
constructor
Create a new Chione::World.
-
#kill_world_threads ⇒ Object
Kill the threads other than the main thread in the world’s thread list.
-
#publish(event_name, *payload) ⇒ Object
Publish an event with the specified
event_nameandpayload. -
#publish_deferred_events ⇒ Object
Send any deferred events to subscribers.
-
#remove_component_from(entity, component) ⇒ Object
(also: #remove_component_for)
Remove the specified
componentfrom the givenentity. -
#remove_manager(manager_type) ⇒ Object
Remove the instance of the specified
manager_typefrom the world and return it if it’s been added. -
#remove_system(system_type) ⇒ Object
Remove the instance of the specified
system_typefrom the world and return it if it’s been added. -
#running? ⇒ Boolean
Returns
trueif the World is running (i.e., if #start has been called). -
#start ⇒ Object
Start the world; returns the Thread in which the world is running.
-
#start_managers ⇒ Object
Start any Managers registered with the world.
-
#start_systems ⇒ Object
Start any Systems registered with the world.
-
#started? ⇒ Boolean
Returns
trueif the World has been started (but is not necessarily running yet). -
#status ⇒ Object
Return a Hash of information about the world suitable for display in tools.
-
#stop ⇒ Object
Stop the world.
-
#stop_managers ⇒ Object
Stop any Managers running in the world.
-
#stop_systems ⇒ Object
Stop any Systems running in the world.
-
#stop_timing_loop ⇒ Object
Halt the main timing loop.
-
#subscribe(event_name, callback = nil, &block) ⇒ Object
Subscribe to events with the specified
event_name. -
#tick(delta_seconds = 1.0/60.0) ⇒ Object
Step the world
delta_secondsinto the future. -
#unsubscribe(callback) ⇒ Object
Unsubscribe from events that publish to the specified
callback.
Methods included from MethodUtilities
attr_predicate, attr_predicate_accessor, singleton_attr_accessor, singleton_attr_reader, singleton_attr_writer, singleton_method_alias, singleton_predicate_accessor, singleton_predicate_reader
Constructor Details
#initialize ⇒ World
Create a new Chione::World
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/chione/world.rb', line 43 def initialize @entities = {} @systems = {} @managers = {} @subscriptions = Hash.new {|h,k| h[k] = Set.new } @defer_events = true @deferred_events = [] @main_thread = nil @world_threads = ThreadGroup.new @entities_by_component = Hash.new {|h,k| h[k] = Set.new } @components_by_entity = Hash.new {|h, k| h[k] = {} } @tick_count = 0 end |
Instance Attribute Details
#components_by_entity ⇒ Object (readonly)
The Hash of Hashes of Components which have been added to an Entity, keyed by the Entity’s ID and the Component class.
98 99 100 |
# File 'lib/chione/world.rb', line 98 def components_by_entity @components_by_entity end |
#deferred_events ⇒ Object (readonly)
The queue of events that have not yet been sent to subscribers.
113 114 115 |
# File 'lib/chione/world.rb', line 113 def deferred_events @deferred_events end |
#entities ⇒ Object (readonly)
The Hash of all Entities in the World, keyed by ID
72 73 74 |
# File 'lib/chione/world.rb', line 72 def entities @entities end |
#entities_by_component ⇒ Object (readonly)
The Hash of Sets of Entities which have a particular component, keyed by Component class.
93 94 95 |
# File 'lib/chione/world.rb', line 93 def entities_by_component @entities_by_component end |
#main_thread ⇒ Object (readonly)
The Thread object running the World’s IO reactor loop
88 89 90 |
# File 'lib/chione/world.rb', line 88 def main_thread @main_thread end |
#managers ⇒ Object (readonly)
The Hash of all Managers currently in the World, keyed by class.
80 81 82 |
# File 'lib/chione/world.rb', line 80 def managers @managers end |
#subscriptions ⇒ Object (readonly)
The Hash of event subscription callbacks registered with the world, keyed by event pattern.
103 104 105 |
# File 'lib/chione/world.rb', line 103 def subscriptions @subscriptions end |
#systems ⇒ Object (readonly)
The Hash of all Systems currently in the World, keyed by class.
76 77 78 |
# File 'lib/chione/world.rb', line 76 def systems @systems end |
#tick_count ⇒ Object
The number of times the event loop has executed.
68 69 70 |
# File 'lib/chione/world.rb', line 68 def tick_count @tick_count end |
#world_threads ⇒ Object (readonly)
The ThreadGroup that contains all Threads managed by the World.
84 85 86 |
# File 'lib/chione/world.rb', line 84 def world_threads @world_threads end |
Instance Method Details
#add_component_to(entity, component, **init_values) ⇒ Object Also known as: add_component_for
Add the specified component to the specified entity.
370 371 372 373 374 375 376 377 378 379 380 381 |
# File 'lib/chione/world.rb', line 370 def add_component_to( entity, component, **init_values ) entity = entity.id if entity.respond_to?( :id ) component = Chione::Component( component, init_values ) component.entity_id = entity self.log.debug "Adding %p for %p" % [ component.class, entity ] self.entities_by_component[ component.class ].add( entity ) component_hash = self.components_by_entity[ entity ] component_hash[ component.class ] = component self.update_entity_caches( entity, component_hash ) end |
#add_manager(manager_type, *args) ⇒ Object
Add an instance of the specified manager_type to the world and return it. It will replace any existing manager of the same type.
483 484 485 486 487 488 489 490 491 492 493 494 |
# File 'lib/chione/world.rb', line 483 def add_manager( manager_type, *args ) manager_obj = manager_type.new( self, *args ) self.managers[ manager_type ] = manager_obj if self.running? self.log.info "Starting %p added to running world." % [ manager_type ] manager_obj.start end self.publish( 'manager/added', manager_obj ) return manager_obj end |
#add_system(system_type, *args) ⇒ Object
Add an instance of the specified system_type to the world and return it. It will replace any existing system of the same type.
440 441 442 443 444 445 446 447 448 449 450 451 |
# File 'lib/chione/world.rb', line 440 def add_system( system_type, *args ) system_obj = system_type.new( self, *args ) self.systems[ system_type ] = system_obj if self.running? self.log.info "Starting %p added to running world." % [ system_type ] system_obj.start end self.publish( 'system/added', system_obj ) return system_obj end |
#call_subscription_callback(callback, event_name, payload) ⇒ Object
Call the specified callback with the provided event_name and payload, returning true if the callback executed without error.
302 303 304 305 306 307 308 309 310 |
# File 'lib/chione/world.rb', line 302 def call_subscription_callback( callback, event_name, payload ) callback.call( event_name, payload ) return true rescue => err self.log.error "%p while calling %p for a %p event: %s" % [ err.class, callback, event_name, err. ] self.log.debug " %s" % [ err.backtrace.join("\n ") ] return false end |
#call_subscription_callbacks(event_name, payload) ⇒ Object
Call the callbacks of any subscriptions matching the specified event_name with the given payload.
286 287 288 289 290 291 292 293 294 295 296 297 |
# File 'lib/chione/world.rb', line 286 def call_subscription_callbacks( event_name, payload ) self.subscriptions.each do |pattern, callbacks| next unless File.fnmatch?( pattern, event_name, File::FNM_EXTGLOB|File::FNM_PATHNAME ) callbacks.each do |callback| unless self.call_subscription_callback( callback, event_name, payload ) self.log.debug "Callback failed; removing it from the subscription." self.unsubscribe( callback ) end end end end |
#components_for(entity) ⇒ Object
Return a Hash of the Component instances associated with entity, keyed by their class.
387 388 389 390 |
# File 'lib/chione/world.rb', line 387 def components_for( entity ) entity = entity.id if entity.respond_to?( :id ) return self.components_by_entity[ entity ].dup end |
#create_blank_entity ⇒ Object
Return a new Chione::Entity with no components for the receiving world. Override this if you wish to use a class other than Chione::Entity for your world.
336 337 338 |
# File 'lib/chione/world.rb', line 336 def create_blank_entity return Chione::Entity.new( self ) end |
#create_entity(archetype = nil) ⇒ Object
Return a new Chione::Entity for the receiving World, using the optional archetype to populate it with components if it’s specified.
319 320 321 322 323 324 325 326 327 328 329 330 |
# File 'lib/chione/world.rb', line 319 def create_entity( archetype=nil ) entity = if archetype archetype.construct_for( self ) else self.create_blank_entity end @entities[ entity.id ] = entity self.publish( 'entity/created', entity.id ) return entity end |
#defer_events ⇒ Object
Whether or not to queue published events instead of sending them to subscribers immediately.
108 |
# File 'lib/chione/world.rb', line 108 attr_predicate_accessor :defer_events |
#destroy_entity(entity) ⇒ Object
Destroy the specified entity and remove it from any registered systems/managers.
343 344 345 346 347 348 349 350 351 |
# File 'lib/chione/world.rb', line 343 def destroy_entity( entity ) raise ArgumentError, "%p does not contain entity %p" % [ self, entity ] unless self.has_entity?( entity ) self.publish( 'entity/destroyed', entity ) self.entities_by_component.each_value {|set| set.delete(entity.id) } self.components_by_entity.delete( entity.id ) @entities.delete( entity.id ) end |
#entities_with(aspect) ⇒ Object
Return an Array of all entities that match the specified aspect.
472 473 474 |
# File 'lib/chione/world.rb', line 472 def entities_with( aspect ) return aspect.matching_entities( self.entities_by_component ) end |
#get_component_for(entity, component_class) ⇒ Object
Return the Component instance of the specified component_class that’s associated with the given entity, if it has one.
395 396 397 398 |
# File 'lib/chione/world.rb', line 395 def get_component_for( entity, component_class ) entity = entity.id if entity.respond_to?( :id ) return self.components_by_entity[ entity ][ component_class ] end |
#has_component_for?(entity, component) ⇒ Boolean
Return true if the specified entity has the given component. If component is a Component subclass, any instance of it will test true. If component is a Component instance, it will only test true if the entity is associated with that particular instance.
424 425 426 427 428 429 430 431 |
# File 'lib/chione/world.rb', line 424 def has_component_for?( entity, component ) entity = entity.id if entity.respond_to?( :id ) if component.is_a?( Class ) return self.components_by_entity[ entity ].key?( component ) else return self.components_by_entity[ entity ][ component.class ] == component end end |
#has_entity?(entity) ⇒ Boolean
Returns true if the world contains the specified entity or an entity with entity as the ID.
356 357 358 359 360 361 362 |
# File 'lib/chione/world.rb', line 356 def has_entity?( entity ) if entity.respond_to?( :id ) return @entities.key?( entity.id ) else return @entities.key?( entity ) end end |
#kill_world_threads ⇒ Object
Kill the threads other than the main thread in the world’s thread list.
211 212 213 214 215 216 217 218 |
# File 'lib/chione/world.rb', line 211 def kill_world_threads self.log.info "Killing child threads." self.world_threads.list.each do |thr| next if thr == @main_thread self.log.debug " killing: %p" % [ thr ] thr.join( Chione::World.max_stop_wait ) end end |
#publish(event_name, *payload) ⇒ Object
Publish an event with the specified event_name and payload.
264 265 266 267 268 269 270 271 |
# File 'lib/chione/world.rb', line 264 def publish( event_name, *payload ) # self.log.debug "Publishing a %p event: %p" % [ event_name, payload ] if self.defer_events? self.deferred_events.push( [event_name, payload] ) else self.call_subscription_callbacks( event_name, payload ) end end |
#publish_deferred_events ⇒ Object
Send any deferred events to subscribers.
275 276 277 278 279 280 281 |
# File 'lib/chione/world.rb', line 275 def publish_deferred_events self.log.debug "Publishing %d deferred events" % [ self.deferred_events.length ] unless self.deferred_events.empty? while event = self.deferred_events.shift self.call_subscription_callbacks( *event ) end end |
#remove_component_from(entity, component) ⇒ Object Also known as: remove_component_for
Remove the specified component from the given entity. If component is a Component subclass, any instance of it will be removed. If it’s a Component instance, it will be removed iff it is the same instance associated with the given entity.
405 406 407 408 409 410 411 412 413 414 415 416 |
# File 'lib/chione/world.rb', line 405 def remove_component_from( entity, component ) entity = entity.id if entity.respond_to?( :id ) if component.is_a?( Class ) self.entities_by_component[ component ].delete( entity ) component_hash = self.components_by_entity[ entity ] component_hash.delete( component ) self.update_entity_caches( entity, component_hash ) else self.remove_component_from( entity, component.class ) if self.has_component_for?( entity, component ) end end |
#remove_manager(manager_type) ⇒ Object
Remove the instance of the specified manager_type from the world and return it if it’s been added. Returns nil if no instance of the specified manager_type was added.
500 501 502 503 504 505 506 507 508 509 510 |
# File 'lib/chione/world.rb', line 500 def remove_manager( manager_type ) manager_obj = self.managers.delete( manager_type ) or return nil self.publish( 'manager/removed', manager_obj ) if self.running? self.log.info "Stopping %p removed from running world." % [ manager_type ] manager_obj.stop end return manager_obj end |
#remove_system(system_type) ⇒ Object
Remove the instance of the specified system_type from the world and return it if it’s been added. Returns nil if no instance of the specified system_type was added.
457 458 459 460 461 462 463 464 465 466 467 468 |
# File 'lib/chione/world.rb', line 457 def remove_system( system_type ) system_obj = self.systems.delete( system_type ) or return nil self.publish( 'system/removed', system_obj ) if self.running? self.log.info "Stopping %p before being removed from runnning world." % [ system_type ] system_obj.stop end return system_obj end |
#running? ⇒ Boolean
Returns true if the World is running (i.e., if #start has been called)
205 206 207 |
# File 'lib/chione/world.rb', line 205 def running? return self.started? && self.tick_count.nonzero? end |
#start ⇒ Object
Start the world; returns the Thread in which the world is running.
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/chione/world.rb', line 128 def start @main_thread = Thread.new do Thread.current.abort_on_exception = true Thread.current.name = "Main World" self.log.info "Main thread (%p) started." % [ Thread.current ] @world_threads.add( Thread.current ) @world_threads.enclose self.start_managers self.start_systems self.timing_loop end self.log.info "Started main World thread: %p" % [ @main_thread ] return @main_thread end |
#start_managers ⇒ Object
Start any Managers registered with the world.
157 158 159 160 161 162 163 164 165 166 |
# File 'lib/chione/world.rb', line 157 def start_managers self.log.info "Starting %d Managers" % [ self.managers.length ] self.managers.each do |manager_class, mgr| self.log.debug " starting %p" % [ manager_class ] start = Time.now mgr.start finish = Time.now self.log.debug " started in %0.5fs" % [ finish - start ] end end |
#start_systems ⇒ Object
Start any Systems registered with the world.
177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/chione/world.rb', line 177 def start_systems self.log.info "Starting %d Systems" % [ self.systems.length ] self.systems.each do |system_class, sys| injections = self.make_injection_hash_for( system_class ) self.log.debug " starting %p" % [ system_class ] start = Time.now sys.start( **injections ) finish = Time.now self.log.debug " started in %0.5fs" % [ finish - start ] end end |
#started? ⇒ Boolean
Returns true if the World has been started (but is not necessarily running yet).
199 200 201 |
# File 'lib/chione/world.rb', line 199 def started? return @main_thread && @main_thread.alive? end |
#status ⇒ Object
Return a Hash of information about the world suitable for display in tools.
117 118 119 120 121 122 123 124 |
# File 'lib/chione/world.rb', line 117 def status return { versions: { chione: Chione::VERSION }, tick: self.tick_count, systems: self.systems.keys.map( &:name ), managers: self.managers.keys.map( &:name ) } end |
#stop ⇒ Object
Stop the world.
222 223 224 225 226 227 |
# File 'lib/chione/world.rb', line 222 def stop self.stop_systems self.stop_managers self.kill_world_threads self.stop_timing_loop end |
#stop_managers ⇒ Object
Stop any Managers running in the world.
170 171 172 173 |
# File 'lib/chione/world.rb', line 170 def stop_managers self.log.info "Stopping managers." self.managers.each {|_, mgr| mgr.stop } end |
#stop_systems ⇒ Object
Stop any Systems running in the world.
192 193 194 195 |
# File 'lib/chione/world.rb', line 192 def stop_systems self.log.info "Stopping systems." self.systems.each {|_, sys| sys.stop } end |
#stop_timing_loop ⇒ Object
Halt the main timing loop. By default, this just kills the world’s main thread.
231 232 233 234 |
# File 'lib/chione/world.rb', line 231 def stop_timing_loop self.log.info "Stopping the timing loop." @main_thread.kill end |
#subscribe(event_name, callback = nil, &block) ⇒ Object
Subscribe to events with the specified event_name. Returns the callback object for later unsubscribe calls.
239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'lib/chione/world.rb', line 239 def subscribe( event_name, callback=nil, &block ) callback ||= block raise LocalJumpError, "no callback given" unless callback raise ArgumentError, "callback is not callable" unless callback.respond_to?( :call ) raise ArgumentError, "callback has wrong arity" unless callback.arity >= 2 || callback.arity < 0 self.subscriptions[ event_name ].add( callback ) return callback end |
#tick(delta_seconds = 1.0/60.0) ⇒ Object
Step the world delta_seconds into the future.
148 149 150 151 152 153 |
# File 'lib/chione/world.rb', line 148 def tick( delta_seconds=1.0/60.0 ) self.publish( 'timing', delta_seconds, self.tick_count ) self.publish_deferred_events self.tick_count += 1 end |
#unsubscribe(callback) ⇒ Object
Unsubscribe from events that publish to the specified callback.
254 255 256 257 258 259 260 |
# File 'lib/chione/world.rb', line 254 def unsubscribe( callback ) self.subscriptions.keys.each do |pattern| cbset = self.subscriptions[ pattern ] cbset.delete( callback ) self.subscriptions.delete( pattern ) if cbset.empty? end end |