Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Notes on framework; adding nil to entities should raise helpful error…

…; beginning swipe at state_machine support
  • Loading branch information...
commit aa45831e428e9158b34c2b1f726dced7892adb0e 1 parent 67734ea
@enebo authored
View
24 README.md
@@ -2,9 +2,25 @@
This is a simple implementation in Ruby of an Entity-Component modelling framework. It was inspired from reading the excellent series on EC modelling by @cbpowell: http://cbpowell.wordpress.com/2012/10/30/entity-component-game-programming-using-jruby-and-libgdx-part-1/
+== Constraints of design
-== Notes
+This particular engine is based around some constraints not mentioned in any EC articles that we have seen before. Many have probably taken similar approaches to wreckem but nothing so far has shown these decisions mentioned publicly. So, beware that this system may not pan out for large and complex games or large data sets.
+
+=== Min-modelling (components are atoms)
+
+Wreckems first major difference is that all components can only contain a single primitive piece of data (or none at all). The rationale for this decision is it is the most flexible possible system for modelling. In one article, they pointed out it was very flexible to have a 2D component containing x,y and then add a new component for just z once you realized you want 3D support in your game. This obviously shows how powerful EC modelling can be but seeing x,y in one component and z in a third looks like a data smell. Having independent X and Y components associated with a position entity seems less smelly. Adding a Z is just adding another component to the entity.
+
+Another interesting observation is seeing warnings in articles about not nesting components in components. From a min-modelling perspective all compound components do just that. Something feel right about this at a very high conceptual level. Is this a smart way to model though? Is it pragmatic?
+
+Min-modelling components might actually not pan out. We don't know. It is something we are trying and so far it is working out ok. The main issue we have so far is efficient retrieval of maps/hashes since non of our components are lists of hashes.
+
+A neat area of min-modelling is we have a single-table design where a single SQL query can get all data for an entity or a set of entities. The data model is ugly in the sense we have nullable fields for unused columns but beautiful in how much information we can get efficiently in a single SQL statement.
+
+=== Components only belong to one thing
+
+This is another big constraint which will add much more data to the db but it also has some significant benefits. Since no two entities can ever contain the same component this means no concurrency issues can exist if you are acting on two entities at the same time. It also means it is trivial to know which entity a component belongs to.
+
+The big disadvantage is much larger live object graph since every component will have a different entity field. So in-memory usage literally is linear to the number of components which all entities can see. In a system which shares components the memory for components could be dramatically cheaper.
+
+Another mystery to be resolved once we make some sizeable games.
-1. Entity or a component may be any matching entity of a component. When
-you care about maintaining a proper relationship in this direction you must
-get in the habit of making n instances of the same component.
View
4 lib/wreckem/entity.rb
@@ -56,8 +56,8 @@ def delete
##
# For boolean components as a nicer looking way of specifying them
- def is(*cclasses)
- cclasses.each do |component_class|
+ def is(*component_classes)
+ component_classes.each do |component_class|
raise ArgumentError unless component_class
add(component_class.new)
end
View
2  lib/wreckem/state_machine.rb
@@ -0,0 +1,2 @@
+require 'wreckem/state_machine/components'
+require 'wreckem/state_machine/state_machine'
View
12 lib/wreckem/state_machine/components.rb
@@ -0,0 +1,12 @@
+require 'wreckem/component'
+
+module Wreckem
+ class StateMachine
+ StateMachine = Wreckem::Component.define
+ Name = Wreckem::Component.define_as_string
+ StateDestinationRef = Wreckem::Component.define_as_ref
+ StateState = Wreckem::Component.define
+ StateTransitionRef = Wreckem::Component.define_as_ref
+ StateExpression = Wreckem::Component.define_as_string
+ end
+end
View
18 lib/wreckem/state_machine/state.rb
@@ -0,0 +1,18 @@
+module Wreckem
+ class State
+ attr_accessor :name, :transitions
+
+ def initialize(name, transitions, goal=false)
+ @name, @transitions = name, transitions
+ end
+
+ def execute(subject, object)
+ return nil if goal
+
+ fired_transition = @transitions.find do |transition|
+ transition.fires?(subject, object)
+ end
+ fired_transition ? fired_transition.destination : self
+ end
+ end
+end
View
62 lib/wreckem/state_machine/state_machine.rb
@@ -0,0 +1,62 @@
+require 'wreckem/state_machine/components'
+require 'wreckem/state_machine/state'
+require 'wreckem/state_machine/transition'
+
+module Wreckem
+ class StateMachine
+ def initialize(name, start_state)
+ @name, @start_state = name, start_state
+ end
+
+ def execute(a, b)
+ state = @start_state
+ while state = state.execute(a, b)
+ end
+ end
+
+ def self.build(machine_def)
+ unless machine_def.is?(StateMachine)
+ raise ArgumentError.new "Not a state machine defintiion #{machine_def.as_string}"
+ end
+
+ name = machine_def.one(Name)
+ start = machine_def.one(StateDestinationRef)
+ start_state = build_state(start).to_entity
+
+ new(name, start_state)
+ end
+
+ def self.build_state(state)
+ raise ArgumentError.new "Not a state entity" if !state.is? StateState
+
+ transitions = state.many(StateTransitionRef).inject([]) do |list, str|
+ list << build_transition(str.entity)
+ end
+
+ Wreckem::State.new(state.one(Name), transitions, state.is?(Goal))
+ end
+
+ def self.build_transition(transition)
+ unless state.is? StateTransition
+ raise ArgumentError.new "Not a state transition"
+ end
+
+ name = transition.one(Name).value
+ expression = transition.one(StateExpression).value
+ destination = transtion.one(StateDestinationRef).to_entity
+
+ cls = eval(<<-EOS)
+Class.new(Wreckem::Transition) do
+ def initialize(name, destination)
+ super(name, destination)
+ end
+
+ def fires?
+ #{expression}
+ end
+end
+EOS
+ cls.new(name, destination)
+ end
+ end
+end
View
9 lib/wreckem/state_machine/transition.rb
@@ -0,0 +1,9 @@
+module Wreckem
+ class Transition
+ attr_accessor :name, :destination
+
+ def initialize(name, destination)
+ @name, @destination = name, destination
+ end
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.