Class: Chione::System

Inherits:
Object
  • Object
show all
Extended by:
MethodUtilities, Loggability, Pluggability
Includes:
Inspection
Defined in:
lib/chione/system.rb

Overview

The System (behavior) class

Direct Known Subclasses

IteratingSystem

Constant Summary collapse

DEFAULT_ASPECT_HASH =

A Hash that auto-vivifies only its :default key

Hash.new do |h,k|
	if k == :default
		h[k] = Chione::Aspect.new
	else
		nil
	end
end

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

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

Methods included from Inspection

#inspect

Constructor Details

#initialize(world) ⇒ System

Create a new Chione::System for the specified world.



114
115
116
117
118
119
120
121
122
# File 'lib/chione/system.rb', line 114

def initialize( world, * )
	self.log.debug "Setting up %p" % [ self.class ]
	@world  = world
	@aspect_entities = self.class.aspects.each_with_object( {} ) do |(aspect_name, aspect), hash|
		matching_set = world.entities_with( aspect )
		self.log.debug "Initial Set with the %s aspect: %p" % [ aspect_name, matching_set]
		hash[ aspect_name ] = matching_set
	end
end

Instance Attribute Details

#aspect_entitiesObject (readonly)

The Hash of Sets of entity IDs which match the System’s aspects, keyed by aspect name.



135
136
137
# File 'lib/chione/system.rb', line 135

def aspect_entities
  @aspect_entities
end

#worldObject (readonly)

The World which the System belongs to



131
132
133
# File 'lib/chione/system.rb', line 131

def world
  @world
end

Class Method Details

.aspect(name, *required, all_of: nil, one_of: nil, none_of: nil) ⇒ Object

Add the specified component_types to the Aspect of this System as being required in any entities it processes.



55
56
57
58
59
60
61
62
63
64
65
# File 'lib/chione/system.rb', line 55

def self::aspect( name, *required, all_of: nil, one_of: nil, none_of: nil )
	aspect = Chione::Aspect.new

	all_of = required + Array( all_of )

	aspect = aspect.with_all_of( all_of )
	aspect = aspect.with_one_of( one_of )   if one_of
	aspect = aspect.with_none_of( none_of ) if none_of

	self.aspects[ name ] = aspect
end

.every_tick(&block) ⇒ Object

Declare a block that is called once every tick for each entity that matches the given aspect.



97
98
99
100
101
# File 'lib/chione/system.rb', line 97

def self::every_tick( &block )
	return self.on( 'timing' ) do |event_name, payload|
		self.instance_exec( *payload, &block )
	end
end

.inherited(subclass) ⇒ Object

Add some per-subclass data structures to inheriting subclasses.



105
106
107
108
109
110
# File 'lib/chione/system.rb', line 105

def self::inherited( subclass )
	super
	subclass.instance_variable_set( :@aspects, DEFAULT_ASPECT_HASH.clone )
	subclass.instance_variable_set( :@event_handlers, self.event_handlers&.dup || [] )
	subclass.instance_variable_set( :@injected_systems, self.injected_systems&.dup || {} )
end

.inject(*systems) ⇒ Object

Dependency-injection: declare one or more systems that should be passed to #start by the running World.



70
71
72
73
74
75
76
77
# File 'lib/chione/system.rb', line 70

def self::inject( *systems )
	systems.each do |system_type|
		system_type = system_type.to_sym
		system_class = Chione::System.get_subclass( system_type )
		attr_accessor( "#{system_type}_system" )
		self.injected_systems[ system_type ] = system_class
	end
end

.on(event_name, &block) ⇒ Object

Declare a block that is called once whenever an event matching event_name is broadcast to the World.

Raises:

  • (LocalJumpError)


82
83
84
85
86
87
88
89
90
91
92
# File 'lib/chione/system.rb', line 82

def self::on( event_name, &block )
	raise LocalJumpError, "no block given" unless block
	raise ArgumentError, "callback has wrong arity" unless block.arity >= 2 || block.arity < 0

	method_name = "on_%s_event" % [ event_name.tr('/', '_') ]
	self.log.debug "Making handler method #%s for %s events out of %p" %
		[ method_name, event_name, block ]
	define_method( method_name, &block )

	self.event_handlers << [ event_name, method_name ]
end

Instance Method Details

#aspectsObject

The Hash of Chione::Aspects that describe entities this system is interested in, keyed by name (a Symbol). A System which declares no aspects will have a :default Aspect which matches all entities.



41
# File 'lib/chione/system.rb', line 41

singleton_attr_reader :aspects

#entities(aspect_name = :default) ⇒ Object

Return an Enumerator that yields the entities which match the given aspect_name.



167
168
169
# File 'lib/chione/system.rb', line 167

def entities( aspect_name=:default )
	return self.aspect_entities[ aspect_name ].to_enum( :each )
end

#entity_components_updated(entity_id, components_hash) ⇒ Object

Entity callback – called whenever an entity has a component added to it or removed from it. Calls the appropriate callback (#inserted or #removed) if the component change caused it to belong to or stop belonging to one of the system’s aspects.



176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/chione/system.rb', line 176

def entity_components_updated( entity_id, components_hash )
	self.class.aspects.each do |aspect_name, aspect|
		entity_ids = self.aspect_entities[ aspect_name ]

		if aspect.matches?( components_hash )
			self.inserted( aspect_name, entity_id, components_hash ) if
				entity_ids.add?( entity_id )
		else
			self.removed( aspect_name, entity_id, components_hash ) if
				entity_ids.delete?( entity_id )
		end
	end
end

#event_handlersObject

Event handler tuples (event name, callback) that should be registered when the System is started.



46
# File 'lib/chione/system.rb', line 46

singleton_attr_reader :event_handlers

#injected_systemsObject

Systems to be injected by the world when this System is started.



50
# File 'lib/chione/system.rb', line 50

singleton_attr_reader :injected_systems

#inserted(aspect_name, entity_id, components) ⇒ Object

Entity callback – called whenever an entity has a component added to it that makes it start matching an aspect of the receiving System. The aspect_name is the name of the Aspect it now matches, and the components are a Hash of the entity’s components keyed by Class. By default this is a no-op.



196
197
198
# File 'lib/chione/system.rb', line 196

def inserted( aspect_name, entity_id, components )
	self.log.debug "Entity %s now matches the %s aspect." % [ entity_id, aspect_name ]
end

#removed(aspect_name, entity_id, components) ⇒ Object

Entity callback – called whenever an entity has a component removed from it that makes it stop matching an aspect of the receiving System. The aspect_name is the name of the Aspect it no longer matches, and the components are a Hash of the entity’s components keyed by Class. By default this is a no-op.



206
207
208
# File 'lib/chione/system.rb', line 206

def removed( aspect_name, entity_id, components )
	self.log.debug "Entity %s no longer matches the %s aspect." % [ entity_id, aspect_name ]
end

#start(**injected_systems) ⇒ Object

Start the system.



139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/chione/system.rb', line 139

def start( **injected_systems )
	self.log.info "Starting the %p system; %d injected systems, %d event handlers to register" %
		[ self.class, injected_systems.length, self.class.event_handlers.length ]

	injected_systems.each do |name, other_system|
		self.public_send( "#{name}_system=", other_system )
	end

	self.class.event_handlers.each do |event_name, method_name|
		callback = self.method( method_name )
		self.log.info "Registering %p as a callback for '%s' events." % [ callback, event_name ]
		self.world.subscribe( event_name, callback )
	end
end

#stopObject

Stop the system.



156
157
158
159
160
161
162
163
# File 'lib/chione/system.rb', line 156

def stop
	self.log.info "Stopping the %p system" % [ self.class ]
	self.class.event_handlers.each do |_, method_name|
		callback = self.method( method_name )
		self.log.info "Unregistering subscription for %p." % [ callback ]
		self.world.unsubscribe( callback )
	end
end