peregrine 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2014 Kevin Owen <solucet@icloud.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # Peregrine
2
+ 1. _adjective: foreign; alien; wandering, traveling, or migrating._
3
+ 2. _noun: a kick-ass falcon._
4
+
5
+ ## Summary
6
+ Peregrine is a highly adaptive, extensible Entity-Component framework written for the Ruby programming language. Peregrine is platform and dependency agnostic, requiring _no_ additional dependencies for use as a library -- and works just as well with JRuby or Rubinius as it does with MRI Ruby.
7
+
8
+ ## Installation
9
+ Peregrine is available as a library in the usual way -- just use RubyGems.
10
+
11
+ ```sh
12
+ $ gem install peregrine
13
+ ```
14
+
15
+ Want to help with development (or hack at the source)? Just clone [the GitHub repository][peregrine], use Bundler, and you're good to go.
16
+
17
+ ```sh
18
+ $ git clone git@github.com:solucet/peregrine.git
19
+ $ gem install bundler
20
+ $ cd peregrine/ && bundle
21
+ ```
22
+
23
+ ## Documentation
24
+ The framework has been heavily documented with information and should serve you well. If you installed Peregrine from the GitHub repository and installed the development dependencies, you can generate a local copy of its RDoc documentation by running `rake rdoc`.
25
+
26
+ If you are unfamiliar with Entity-Component design, it is highly recommended that you read about it in addition to reading the documentation for the Peregrine framework. Chris Powell wrote a [great series of tutorials][recf-tutorial] on using EC design in JRuby on his blog which also includes links to further reading on the subject.
27
+
28
+ ## Thanks
29
+ Special thanks go to Chris Powell, as the Peregrine framework was conceptually inspired by his [Ruby Entity-Component Framework][recf] and its related tutorials (which are definitely recommended reading).
30
+
31
+ ## License
32
+ Peregrine is made available under the terms of the MIT License. See the included LICENSE file for more information.
33
+
34
+ [peregrine]: https://github.com/solucet/peregrine
35
+ [recf]: https://github.com/cpowell/ruby-entity-component-framework
36
+ [recf-tutorial]: http://cbpowell.wordpress.com/2012/10/30/
@@ -0,0 +1,12 @@
1
+ require 'peregrine/collections/named'
2
+ require 'peregrine/collections/tagged'
3
+
4
+ module Peregrine
5
+ module Collections
6
+ # Includes instance method definitions available in the Collections::Named
7
+ # and Collections::Tagged modules.
8
+ module Common
9
+ include Named, Tagged
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,33 @@
1
+ module Peregrine
2
+ module Collections
3
+ # Provides methods for filtering Entity objects contained in a collection.
4
+ # This module is intended to be an extension to existing collection
5
+ # instances.
6
+ module Composite
7
+ # Returns an array of all Entities in the collection which include all of
8
+ # the given Component classes. Yields the matching Entity instances if a
9
+ # block is given.
10
+ def with_components(*list)
11
+ entities = select do |item|
12
+ next unless item.respond_to?(:component_classes)
13
+ list.all? { |component| item.component_classes.include?(component) }
14
+ end
15
+ entities.each { |entity| yield entity } if block_given?
16
+ entities.extend(Collections)
17
+ end
18
+ alias :with_component :with_components
19
+
20
+ # Returns an array of all Entities in the collection which include _any_
21
+ # of the given Component classes. Yields the matching Entity instances if
22
+ # a block is given.
23
+ def with_any_component(*list)
24
+ entities = select do |item|
25
+ next unless item.respond_to?(:component_classes)
26
+ !(item.component_classes & list).empty?
27
+ end
28
+ entities.each { |entity| yield entity } if block_given?
29
+ entities.extend(Collections)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,34 @@
1
+ module Peregrine
2
+ module Collections
3
+ # Provides methods for filtering collections by item names. This module is
4
+ # intended to be an extension to existing collection instances.
5
+ module Named
6
+ # Returns an array of all items with a name that matches the given
7
+ # matcher. The matcher may be a Regexp for fine-tuned matching or any
8
+ # other object for specific equality matching. Yields the matching items
9
+ # in the collection if a block is given.
10
+ #
11
+ # ==== Examples
12
+ #
13
+ # Using a regular expression as a matcher:
14
+ # manager = Peregrine::EntityManager.new do |manager|
15
+ # manager.spawn { |entity| entity.name = 'Example' }
16
+ # end
17
+ # manager.entities.named(/^Ex/) # => [ Entity 'Example' ... ]
18
+ #
19
+ # Using a string as a matcher:
20
+ # manager = Peregrine::EntityManager.new do |manager|
21
+ # manager.spawn { |entity| entity.name = 'Example' }
22
+ # end
23
+ # manager.entities.named('Example') # => [ Entity 'Example' ... ]
24
+ def named(matcher)
25
+ items = select do |item|
26
+ next unless item.respond_to?(:name)
27
+ matcher.is_a?(Regexp) ? item.name[matcher] : item.name == matcher.to_s
28
+ end
29
+ items.each { |item| yield item } if block_given?
30
+ items.extend(Collections)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,30 @@
1
+ module Peregrine
2
+ module Collections
3
+ # Provides methods for filtering System objects contained in a collection.
4
+ # This module is intended to be an extension to existing collection
5
+ # instances.
6
+ module Systemic
7
+ # Returns an array of enabled System objects in the collection. Yields the
8
+ # matching System instances if a block is given.
9
+ def enabled
10
+ systems = select do |system|
11
+ next unless system.respond_to?(:enabled?)
12
+ system.enabled?
13
+ end
14
+ systems.each { |system| yield system } if block_given?
15
+ systems.extend(Collections)
16
+ end
17
+
18
+ # Returns an array of disabled System objects in the collection. Yields
19
+ # the matching System instances if a block is given.
20
+ def disabled
21
+ systems = select do |system|
22
+ next unless system.respond_to?(:enabled?)
23
+ !system.enabled?
24
+ end
25
+ systems.each { |system| yield system } if block_given?
26
+ systems.extend(Collections)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,38 @@
1
+ module Peregrine
2
+ module Collections
3
+ # Provides methods for filtering collections by item tags. This module is
4
+ # intended to be an extension to existing collection instances.
5
+ module Tagged
6
+ # Returns an array of objects within this collection with all of the given
7
+ # tags. Yields the tagged items in the collection if a block is given.
8
+ def tagged(*list)
9
+ items = select do |item|
10
+ next unless item.respond_to?(:tags)
11
+ list.all? { |tag| item.tags.include?(tag) }
12
+ end
13
+ items.each { |item| yield item } if block_given?
14
+ items.extend(Collections)
15
+ end
16
+
17
+ # Returns an array of objects within this collection that contain any of
18
+ # the given tags. Yields the tagged items in the collection if a block is
19
+ # given.
20
+ def any_tagged(*list)
21
+ items = select do |item|
22
+ next unless item.respond_to?(:tags)
23
+ !(item.tags & list).empty?
24
+ end
25
+ items.each { |item| yield item } if block_given?
26
+ items.extend(Collections)
27
+ end
28
+
29
+ # Returns an array of objects in this collection which are not tagged.
30
+ # Yields the untagged items in the collection if a block is given.
31
+ def untagged
32
+ items = select { |i| i.respond_to?(:tags) ? i.tags.empty? : true }
33
+ items.each { |item| yield item } if block_given?
34
+ items.extend(Collections)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,25 @@
1
+ # Require all Ruby files in the 'collections/' directory. Using +File.join+ to
2
+ # ensure that all files are properly required regardless of the directory
3
+ # separator in use.
4
+ Dir[File.join(File.dirname(__FILE__), 'collections', '*.rb')].each do |file|
5
+ require file
6
+ end
7
+
8
+ module Peregrine
9
+ # This module provides modules which define instance methods to be used by
10
+ # collections (such as arrays or hashes) with methods for filtering thier
11
+ # collected items. These modules are designed to be extensions to instances of
12
+ # collections so as not to pollute the default Ruby collections.
13
+ #
14
+ # Note that arrays returned from any of these modules are already extended
15
+ # with all of the defined collection modules, facilitating chaining of
16
+ # collection filtering methods.
17
+ module Collections
18
+ # Extends all of the constants defined within this module to the parent
19
+ # which is extended by this module -- essentially a shortcut to extend with
20
+ # all of the defined collections.
21
+ def self.extended(parent)
22
+ parent.send(:extend, *constants.map { |const| const_get(const) })
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,59 @@
1
+ require 'peregrine/features'
2
+
3
+ module Peregrine
4
+ # == Summary
5
+ #
6
+ # Components serve as data storage for Entity objects. Component objects, by
7
+ # definition, should _not_ contain any logic -- they serve only as a way to
8
+ # store information or as a "flag" for an Entity to be used by a System to
9
+ # implement the actual logic.
10
+ #
11
+ # == Usage
12
+ #
13
+ # It is expected that developers will subclass the Component class in order
14
+ # to create their own individual Components. When subclassing the Component,
15
+ # it is important to note that you should _not_ overwrite the +#initialize+
16
+ # method, and instead overwite the +#initialize_data+ method in order to
17
+ # instantiate a Component. Any additional arguments given to +Component.new+
18
+ # are passed along to the +#initialize_data+ method.
19
+ #
20
+ # == Example
21
+ #
22
+ # class Mortal < Peregrine::Component
23
+ # attr_reader :health
24
+ #
25
+ # # Initializing the data for this Component object.
26
+ # def initialize_data(health = 100, max_health = 100)
27
+ # @health = health
28
+ # @maximum = max_health
29
+ # end
30
+ #
31
+ # # Do not implement logic, but define limits for data storage.
32
+ # def health=(value)
33
+ # @health = [0, value, @maximum].sort[1]
34
+ # end
35
+ # end
36
+ class Component
37
+ include Features
38
+
39
+ # Create a new Component instance. Any arguments given to this method are
40
+ # passed to the +initialize_data+ method. Yields the newly instanced
41
+ # Component if a block is given.
42
+ def initialize(*data_args)
43
+ initialize_data(*data_args)
44
+ yield self if block_given?
45
+ end
46
+
47
+ # Intended to be overwritten by subclasses of the Component to initialize
48
+ # the data used. Additional arguments passed to +Component.new+ are passed
49
+ # to this method directly. This method does nothing on its own.
50
+ def initialize_data
51
+ end
52
+
53
+ # Presents a human-readable summary of the Component.
54
+ def to_s
55
+ "Component '#{name}' #{id}"
56
+ end
57
+ alias :inspect :to_s
58
+ end
59
+ end
@@ -0,0 +1,130 @@
1
+ require 'peregrine/collections/common'
2
+ require 'peregrine/features'
3
+ require 'securerandom'
4
+
5
+ module Peregrine
6
+ # == Summary
7
+ #
8
+ # An Entity is any object within your project -- literally almost anything.
9
+ # Entities are designed to hold Component objects which determine what the
10
+ # Entity contains in terms of data. Components are also regularly used to
11
+ # "flag" an Entity -- for example, adding an empty Component +Renderable+
12
+ # would flag to System objects that this Entity can be rendered.
13
+ #
14
+ # Essentially, Entity objects are little more than an identifier which serves
15
+ # as storage for Component objects. They do nothing more than store
16
+ # Components and provide methods for adding, removing, and filtering the
17
+ # contained Components.
18
+ #
19
+ # == Usage
20
+ #
21
+ # In general, it's best not to use Entity objects directly, but to make use
22
+ # of EntityManager objects which serve to organize and manage collections of
23
+ # Entity instances. See the EntityManager class' documentation for more
24
+ # information on how to use them.
25
+ class Entity
26
+ include Features
27
+
28
+ # Array of Component objects attached to this Entity.
29
+ attr_reader :components
30
+
31
+ # Symbol representing the UUID of this Entity.
32
+ attr_reader :uuid
33
+
34
+ # Creates a new Entity instance and adds the given Component constants or
35
+ # instances to the Entity. Yields the newly created Entity if a block is
36
+ # given.
37
+ #
38
+ # ==== Example
39
+ #
40
+ # Peregrine::Entity.new(Peregrine::Component) do |instance|
41
+ # # Entity names are optional, but let's name this one.
42
+ # instance.name = 'Example'
43
+ # end # => Entity 'Example' 0xdf7258 (1)
44
+ def initialize(*components)
45
+ @components = [].extend(Collections::Common)
46
+ @uuid = SecureRandom.uuid.to_sym
47
+ add_components(*components)
48
+ yield self if block_given?
49
+ end
50
+
51
+ # Adds the given Component constants or instances to this Entity. Constants
52
+ # given are automatically instanced. Ignores Component classes which are
53
+ # already present in the Entity. Returns the array of Component objects
54
+ # attached to this Entity after all additions.
55
+ #
56
+ # ==== Example
57
+ #
58
+ # class Example < Peregrine::Component ; end
59
+ #
60
+ # entity = Peregrine::Entity.new
61
+ # entity.add_components(Example) # => [Component 'Example' ...]
62
+ def add_components(*components)
63
+ components.each do |component|
64
+ component = component.new if component.class == Class
65
+ unless component_classes.include?(component.class)
66
+ @components.push(component)
67
+ end
68
+ end
69
+ @components
70
+ end
71
+ alias :add_component :add_components
72
+
73
+ # Removes Component objects of the given classes from the Entity. Returns an
74
+ # array of the removed Component objects.
75
+ def remove_components!(*components)
76
+ removed = []
77
+ components.each do |flag|
78
+ @components.reject! { |c| c.class == flag ? removed.push(c) : false }
79
+ end
80
+ removed.extend(Collections::Common)
81
+ end
82
+ alias :remove_component! :remove_components!
83
+
84
+ # Returns an array of all Component classes attached to this Entity. Yields
85
+ # the Component classes if a block is given.
86
+ def component_classes
87
+ components = @components.map { |component| component.class }.uniq
88
+ components.each { |component| yield component } if block_given?
89
+ components.extend(Collections::Common)
90
+ end
91
+
92
+ # Returns all of the Component objects attached to this Entity of the exact
93
+ # given class. Yields the Component objects if a block is given.
94
+ #
95
+ # ==== Example
96
+ #
97
+ # class Example < Peregrine::Component ; end
98
+ #
99
+ # entity = Peregrine::Entity.new(Example)
100
+ # entity.components_of_class(Example) # => [Component 'Example' ...]
101
+ def components_of_class(const)
102
+ components = @components.select { |c| c.class == const }
103
+ components.each { |component| yield component } if block_given?
104
+ components.extend(Collections::Common)
105
+ end
106
+
107
+ # Returns all of the Component objects attached to this Entity descended
108
+ # from the given class or module. Yields the Component objects if a block is
109
+ # given.
110
+ #
111
+ # ==== Example
112
+ #
113
+ # class Example < Peregrine::Component ; end
114
+ #
115
+ # entity = Peregrine::Entity.new(Peregrine::Component, Example)
116
+ # entity.components_of_kind(Peregrine::Component)
117
+ # # => [Component 'Peregrine::Component' ..., Component 'Example' ...]
118
+ def components_of_kind(const)
119
+ components = @components.select { |c| c.class.ancestors.include?(const) }
120
+ components.each { |component| yield component } if block_given?
121
+ components.extend(Collections::Common)
122
+ end
123
+
124
+ # Presents a human-readable summary of the Entity.
125
+ def to_s
126
+ "Entity '#{name}' #{uuid} (#{@components.size})"
127
+ end
128
+ alias :inspect :to_s
129
+ end
130
+ end
@@ -0,0 +1,164 @@
1
+ require 'peregrine/collections'
2
+ require 'peregrine/entity'
3
+ require 'peregrine/features'
4
+
5
+ module Peregrine
6
+ # == Summary
7
+ #
8
+ # The EntityManager is an object designed to create, organize, and filter an
9
+ # array of Entity objects and manage System objects which implement logic for
10
+ # those Entities. Essentially, the EntityManager serves to group related
11
+ # Entity objects and provide them with the Systems that act upon them.
12
+ #
13
+ # == Usage
14
+ #
15
+ # The EntityManager is designed to group related Entity objects with the
16
+ # System instances which provide their logic. As such, it is recommended that
17
+ # you utilize as many instances of EntityManager objects as your application
18
+ # requires.
19
+ #
20
+ # EntityManager objects automatically extend their arrays of Entity and System
21
+ # objects with instance methods designed to facilitate filtering of the
22
+ # managed items each respectively contain. Entities may be filtered by name,
23
+ # included (or excluded) tags, and which Component classes they may or may not
24
+ # contain. Likewise, System objects may be filtered based on any tags they do
25
+ # or do not have as well as by name. For more detailed information about the
26
+ # available filtering methods, please see the documentation for the
27
+ # Peregrine::Collections module and the modules that it contains.
28
+ #
29
+ # Note that Peregrine itself does not include any objects for grouping
30
+ # EntityManager instances together (unlike some other frameworks) -- this
31
+ # functionality is left up to individual developers and their preferred
32
+ # methods. Use your EntityManagers in whatever way you personally feel would
33
+ # best benefit _you_ and _your_ application.
34
+ #
35
+ # == Examples
36
+ #
37
+ # Naming EntityManager instances, spawning Entity objects, and filtering
38
+ # Entity objects based on tags:
39
+ #
40
+ # class Example < Peregrine::Component ; end
41
+ #
42
+ # manager = Peregrine::EntityManager.new { |m| m.name = 'Example' }
43
+ # 5.times do |iterator|
44
+ # iterator += 1
45
+ # manager.spawn(Example) do |entity|
46
+ # entity.name = iterator
47
+ # iterator.even? ? entity.add_tag(:even) : entity.add_tag(:odd)
48
+ # end
49
+ # end
50
+ # manager.entities.tagged(:even) # => [Entity '2' ..., Entity '4' ...]
51
+ #
52
+ # Finding all basic entities and adding a component to them with a block:
53
+ #
54
+ # manager = Peregrine::EntityManager.new
55
+ # rand(1..20).times { manager.spawn }
56
+ # manager.basic_entities do |entity|
57
+ # e.add_component(Peregrine::Component)
58
+ # end
59
+ class EntityManager
60
+ include Features
61
+
62
+ # Array of entities owned by this EntityManager.
63
+ attr_reader :entities
64
+
65
+ # Array of Systems operating within this EntityManager.
66
+ attr_reader :systems
67
+
68
+ # Creates a new EntityManager instance. Yields the newly created instance if
69
+ # a block is given.
70
+ def initialize
71
+ @entities = [].extend(Collections::Common, Collections::Composite)
72
+ @systems = [].extend(Collections::Common, Collections::Systemic)
73
+ yield self if block_given?
74
+ end
75
+
76
+ # Calls +process+ on each enabled System within this EntityManager. The
77
+ # +process+ method is _intended_ to be called once per tick, but the actual
78
+ # implementation is left up to the developer. Returns the array of System
79
+ # objects operating within the EntityManager.
80
+ def process
81
+ @systems.each { |system| system.process if system.enabled? }
82
+ end
83
+
84
+ # Spawns a new Entity with the given Components and pushes the new Entity
85
+ # into the array of owned entities. Returns the spawned Entity. Yields the
86
+ # new Entity before pushing it into the owned entities array if a block is
87
+ # given.
88
+ def spawn(*arguments)
89
+ entity = Peregrine::Entity.new(*arguments)
90
+ yield entity if block_given?
91
+ @entities.push(entity)
92
+ entity
93
+ end
94
+
95
+ # Adds the given Entity instances (or instantiated Entity constants) to the
96
+ # EntityManager. Returns the array of managed Entity objects.
97
+ def add_entities(*entities)
98
+ entities.each do |entity|
99
+ entity = entity.new if entity.class == Class
100
+ @entities.push(entity)
101
+ end
102
+ @entities
103
+ end
104
+ alias :add_entity :add_entities
105
+
106
+ # Removes the given Entity instances from the EntityManager. Returns the
107
+ # removed Entity objects. Intended to be used with the EntityManager's
108
+ # filtering methods to select appropriate Entity instances.
109
+ def remove_entities!(*entities)
110
+ removed = []
111
+ @entities.reject! do |entity|
112
+ entities.include?(entity) ? removed.push(entity) : false
113
+ end
114
+ removed.extend(Collections::Common, Collections::Composite)
115
+ end
116
+ alias :remove_entity! :remove_entities!
117
+
118
+ # Returns an array of all managed Entity objects without Components. Yields
119
+ # the array of basic Entities if a block is given. Note that "basic" Entity
120
+ # objects _may_ still be named or tagged.
121
+ def basic_entities
122
+ entities = @entities.select { |e| e.components.empty? }
123
+ entities.each { |entity| yield entity } if block_given?
124
+ entities.extend(Collections::Common, Collections::Composite)
125
+ end
126
+
127
+ # Adds the given System instances (or instantiated System constants) to the
128
+ # EntityManager, updating the System's manager. Returns the array of System
129
+ # objects operating in the EntityManager.
130
+ def add_systems(*systems)
131
+ systems.each do |system|
132
+ if system.class == Class
133
+ system = system.new(self)
134
+ else
135
+ @systems.push(system)
136
+ system.manager = self
137
+ end
138
+ end
139
+ @systems
140
+ end
141
+ alias :add_system :add_systems
142
+
143
+ # Removes the given System instances (or all System classes of the given
144
+ # constants) from the EntityManager, updating the manager of each System.
145
+ # Returns an array of the removed System instances.
146
+ def remove_systems!(*systems)
147
+ removed = []
148
+ @systems.reject! do |system|
149
+ if systems.include?(system) || systems.include?(system.class)
150
+ system.manager = nil
151
+ removed.push(system)
152
+ else false end
153
+ end
154
+ removed.extend(Collections::Common, Collections::Systemic)
155
+ end
156
+
157
+ # Presents a human-readable summary of the EntityManager.
158
+ def to_s
159
+ "Entity Manager '#{name}' #{id} " <<
160
+ "(#{@entities.size} Entities, #{@systems.size} Systems)"
161
+ end
162
+ alias :inspect :to_s
163
+ end
164
+ end
@@ -0,0 +1,20 @@
1
+ module Peregrine
2
+ module Features
3
+ # Provides the +configure+ method. Intended to be included in classes
4
+ # requiring this functionality.
5
+ module Configurable
6
+ # Yields the object this method is called on. This method is intended to
7
+ # allow configuration of Peregrine objects with other features. Returns
8
+ # the object this method was called on.
9
+ #
10
+ # ==== Example
11
+ #
12
+ # entity = Peregrine::Entity.new
13
+ # entity.configure { |e| e.name = 'Example' } # => Entity 'Example' ...
14
+ def configure
15
+ yield self if block_given?
16
+ self
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,12 @@
1
+ module Peregrine
2
+ module Features
3
+ # Provides the +id+ method to extract an object's +object_id+ as a hex
4
+ # string. Intended to be included in classes requiring this functionality.
5
+ module Identifiable
6
+ # Returns the object's +object_id+ as a hexadecimal string.
7
+ def id
8
+ '0x' << (object_id << 1).to_s(16)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ module Peregrine
2
+ module Features
3
+ # Provides methods for naming objects. Intended to be included in classes
4
+ # requiring this functionality.
5
+ module Nameable
6
+ # Returns the name of the object. Lazily evaluated.
7
+ def name
8
+ @name ||= self.class.to_s
9
+ end
10
+
11
+ # Sets the name of the object to the given value after String coercion.
12
+ def name=(value)
13
+ @name = value.to_s
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,32 @@
1
+ module Peregrine
2
+ module Features
3
+ # Provides methods for adding and removing tags to and from objects. This
4
+ # essentially provides yet another method for identifying and filtering
5
+ # various objects. Intended to be included in classes requiring this
6
+ # functionality.
7
+ module Taggable
8
+ # Returns the array of tags this object contains. Lazily evaluated.
9
+ def tags
10
+ @tags ||= []
11
+ end
12
+
13
+ # Add the given tags to the object. Tags may be any kind of object. Tags
14
+ # identical to existing tags are ignored. Returns an array of all tags
15
+ # this object contains.
16
+ def add_tags(*list)
17
+ tags.push(*list).uniq!
18
+ tags
19
+ end
20
+ alias :add_tag :add_tags
21
+
22
+ # Removes the given tags from the object. Returns an array of the removed
23
+ # tags.
24
+ def remove_tags!(*list)
25
+ removed = []
26
+ tags.reject! { |tag| list.include?(tag) ? removed.push(tag) : false }
27
+ removed
28
+ end
29
+ alias :remove_tag! :remove_tags!
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,20 @@
1
+ # Require all Ruby files in the 'features/' directory. Using +File.join+ to
2
+ # ensure that all files are properly required regardless of the directory
3
+ # separator in use.
4
+ Dir[File.join(File.dirname(__FILE__), 'features', '*.rb')].each do |file|
5
+ require file
6
+ end
7
+
8
+ module Peregrine
9
+ # This module provides modules which add instance methods to Peregrine objects
10
+ # that provide functionality such as tags, names, and identifiers. These
11
+ # modules are designed to be included in Peregrine classes.
12
+ module Features
13
+ # Includes all of the constants defined within this module to the parent
14
+ # which includes this module -- essentially a shortcut to include all of the
15
+ # defined features.
16
+ def self.included(parent)
17
+ parent.send(:include, *constants.map { |const| const_get(const) })
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,129 @@
1
+ require 'peregrine/collections'
2
+ require 'peregrine/features'
3
+
4
+ module Peregrine
5
+ # == Summary
6
+ #
7
+ # The System class implements logic for a collection of Entity objects and
8
+ # resides within an EntityManager instance. The System class present in the
9
+ # Peregrine framework serves as a basis for creating subclasses to implement
10
+ # logic and perform no such logic themselves (in fact, they raise a
11
+ # +NotImplementedError+ when processed).
12
+ #
13
+ # == Usage
14
+ #
15
+ # The default System class is very minimal and performs no actual logic on its
16
+ # own. Individual developers are intended to subclass the System and implement
17
+ # the needed logic in their own +process+ methods.
18
+ #
19
+ # In addition to this, System classes may be enabled or disabled. Disabled
20
+ # System classes may still be explicitly updated by calling their +process+
21
+ # method, but will not automatically fire when the EntityManager that they
22
+ # belong to calls its +process+ method. Systems are enabled by default, but
23
+ # may be disabled when instanced by passing a block or calling the +configure+
24
+ # method with a block.
25
+ #
26
+ # Most System classes are expected to operate on a limited subset of the
27
+ # available Entity objects within the EntityManager that contains the System.
28
+ # As such, the +selector+ method has been provided to allow filtering the
29
+ # EntityManager's managed Entity objects. The +selector+ method returns a
30
+ # Proc object which is used by the +select+ method on the +entities+ array
31
+ # owned by the EntityManager. When overwritten, the +selector+ method ensures
32
+ # that the +entities+ method of the System only returns Entity objects that
33
+ # pass through the +selector+ method's block.
34
+ #
35
+ # Given the implementation of the +selector+, it is _highly_ recommended that
36
+ # you wrap your Systems' +process+ methods in the +entities+ method to make
37
+ # sure that they only implement logic for the properly selected Entities.
38
+ #
39
+ # == Example
40
+ #
41
+ # To demonstrate how to write a System subclass, we'll build a simple System
42
+ # that simply acts on Entity objects that contain Components and removes the
43
+ # Components when processed.
44
+ #
45
+ # class Remover < Peregrine::System
46
+ # def selector
47
+ # ->(entity) { !entity.components.empty? }
48
+ # end
49
+ #
50
+ # def process
51
+ # entities.each do |entity|
52
+ # entity.remove_components!(*entity.component_classes)
53
+ # end
54
+ # end
55
+ # end
56
+ #
57
+ # Now we'll create a new, disabled instance of the Remover System.
58
+ #
59
+ # manager = Peregrine::EntityManager.new
60
+ # manager.add_system(Remover.new { |system| system.disable })
61
+ class System
62
+ include Features
63
+
64
+ # The EntityManager object using this System instance.
65
+ attr_accessor :manager
66
+
67
+ # Creates a new System instance operating within the given EntityManager
68
+ # and automatically adds the System to the EntityManager. Yields the newly
69
+ # created System if a block is given.
70
+ #
71
+ # ==== Example
72
+ #
73
+ # manager = Peregrine::EntityManager.new { |m| m.name = 'Example' }
74
+ # Peregrine::System.new(manager) do |system|
75
+ # system.name = 'Example'
76
+ # # Systems are enabled by default, but we want this one disabled.
77
+ # system.disable
78
+ # end # => - System 'Example' 0x1724d80 <Entity Manager 'Example' ...>
79
+ def initialize(manager = nil)
80
+ @enabled = true
81
+ @manager = manager
82
+ @manager.systems.push(self) if @manager.respond_to?(:systems)
83
+ yield self if block_given?
84
+ end
85
+
86
+ # Provides the block to be given to the +entities+ method's +select+ call
87
+ # used to filter the Entity objects affected by the System. The default
88
+ # +selector+ implementation returns all Entity objects that are not +false+
89
+ # or +nil+. Intended to be overwritten in subclasses.
90
+ def selector
91
+ Proc.new { |entity| entity }
92
+ end
93
+
94
+ # Returns an array of all of the Entity objects that this System should act
95
+ # upon. This method is intended to be used in the body of a subclass'
96
+ # +update+ method in order to facilitate only operating on the desired
97
+ # Entity objects. Entity objects are collected by calling +select+ on the
98
+ # EntityManager's +entities+ array with a block supplied by the System's
99
+ # +selector+ method. Yields each selected Entity object if a block is given.
100
+ def entities
101
+ return [] unless @manager.respond_to?(:entities)
102
+ @manager.entities.select(&selector).each do |entity|
103
+ yield entity if block_given?
104
+ end
105
+ end
106
+
107
+ # Explicitly enables the System.
108
+ def enable() @enabled = true end
109
+
110
+ # Explicitly disables the System.
111
+ def disable() @enabled = false end
112
+
113
+ # Explicitly returns +true+ if the System is enabled, +false+ otherwise.
114
+ def enabled?() @enabled ? true : false end
115
+
116
+ # Called whenever the EntityManager with this System is updated. This method
117
+ # is intended to be overwritten in subclasses by developers in order to
118
+ # implement logic. Only called by the EntityManager if the System is
119
+ # enabled. Raises a +NotImplementedError+ by default.
120
+ def process() raise NotImplementedError end
121
+
122
+ # Presents a human-readable summary of the System.
123
+ def to_s
124
+ status = enabled? ? '+' : '-'
125
+ "#{status} System '#{name}' #{id} <#{manager.name} #{manager.id}>"
126
+ end
127
+ alias :inspect :to_s
128
+ end
129
+ end
@@ -0,0 +1,4 @@
1
+ module Peregrine
2
+ # Semantic version of the Peregrine framework.
3
+ VERSION = '0.1.0'
4
+ end
data/lib/peregrine.rb ADDED
@@ -0,0 +1,6 @@
1
+ # Require all Ruby files in the 'peregrine/' directory. Using +File.join+ to
2
+ # ensure that all files are properly required regardless of the directory
3
+ # separator in use.
4
+ Dir[File.join(File.dirname(__FILE__), 'peregrine', '*.rb')].each do |file|
5
+ require file
6
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: peregrine
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Kevin Owen
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-02-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rdoc
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Peregrine is a minimal, highly adaptive Entity-Component design framework
63
+ for the Ruby scripting language designed for general-purpose programming.
64
+ email:
65
+ - solucet@icloud.com
66
+ executables: []
67
+ extensions: []
68
+ extra_rdoc_files: []
69
+ files:
70
+ - lib/peregrine/collections/common.rb
71
+ - lib/peregrine/collections/composite.rb
72
+ - lib/peregrine/collections/named.rb
73
+ - lib/peregrine/collections/systemic.rb
74
+ - lib/peregrine/collections/tagged.rb
75
+ - lib/peregrine/collections.rb
76
+ - lib/peregrine/component.rb
77
+ - lib/peregrine/entity.rb
78
+ - lib/peregrine/entity_manager.rb
79
+ - lib/peregrine/features/configurable.rb
80
+ - lib/peregrine/features/identifiable.rb
81
+ - lib/peregrine/features/nameable.rb
82
+ - lib/peregrine/features/taggable.rb
83
+ - lib/peregrine/features.rb
84
+ - lib/peregrine/system.rb
85
+ - lib/peregrine/version.rb
86
+ - lib/peregrine.rb
87
+ - LICENSE
88
+ - README.md
89
+ homepage: https://github.com/solucet/peregrine
90
+ licenses:
91
+ - MIT
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ! '>='
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubyforge_project:
110
+ rubygems_version: 1.8.23
111
+ signing_key:
112
+ specification_version: 3
113
+ summary: Flexible Entity-Component framework for Ruby.
114
+ test_files: []