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 +19 -0
- data/README.md +36 -0
- data/lib/peregrine/collections/common.rb +12 -0
- data/lib/peregrine/collections/composite.rb +33 -0
- data/lib/peregrine/collections/named.rb +34 -0
- data/lib/peregrine/collections/systemic.rb +30 -0
- data/lib/peregrine/collections/tagged.rb +38 -0
- data/lib/peregrine/collections.rb +25 -0
- data/lib/peregrine/component.rb +59 -0
- data/lib/peregrine/entity.rb +130 -0
- data/lib/peregrine/entity_manager.rb +164 -0
- data/lib/peregrine/features/configurable.rb +20 -0
- data/lib/peregrine/features/identifiable.rb +12 -0
- data/lib/peregrine/features/nameable.rb +17 -0
- data/lib/peregrine/features/taggable.rb +32 -0
- data/lib/peregrine/features.rb +20 -0
- data/lib/peregrine/system.rb +129 -0
- data/lib/peregrine/version.rb +4 -0
- data/lib/peregrine.rb +6 -0
- metadata +114 -0
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
|
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: []
|