peregrine 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []