kiwi-ecs 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b17b772e56b3533bac84c71c609eb8544908667c4cac99725327399d1fd44fed
4
+ data.tar.gz: d8669b034e3db56f038919b60aa8f7f2b36eb6b98e7f12d22f533ac2c2680454
5
+ SHA512:
6
+ metadata.gz: f5e2f0b3770ffe2eeebd64f590aec863fc8074f4ec1331578f1a7f5ee42ac69e8ca257c3cf9717f8e7b43c876dbe80f946e9e74045e0a4efa9feaeb78b8b05c7
7
+ data.tar.gz: a42b7f00ee919b7cd1d37f70c0a19f1b217d429f604f67e0a99734c4de4336d7bcecea9235e244fb884c93288f06df38c9b9e4a453520e7ce86809c92ad5a6e0
data/lib/arch_store.rb ADDED
@@ -0,0 +1,30 @@
1
+ require_relative 'archetype'
2
+
3
+ class ArchStore
4
+ # [[Integer]:Integer]
5
+ attr_accessor :compMap
6
+
7
+ def initialize
8
+ # [Archetype]
9
+ @archetypes = []
10
+ # [[ComponentId]:ArchetypeId]
11
+ @compMap = Hash.new
12
+ end
13
+
14
+ def get(archetypeId)
15
+ return @archetypes[archetypeId]
16
+ end
17
+
18
+ # @params [Array<Integer>] componentIds: a sorted array of component ids
19
+ def get_archetype_id(componentIds)
20
+ archId = @compMap[componentIds]
21
+ if archId != nil
22
+ return archId
23
+ else
24
+ id = @archetypes.size
25
+ @archetypes.push Archetype.new(componentIds)
26
+ @compMap[componentIds] = id
27
+ return id
28
+ end
29
+ end
30
+ end
data/lib/archetype.rb ADDED
@@ -0,0 +1,93 @@
1
+ ComponentColumn = Struct.new(:components)
2
+
3
+ class Archetype
4
+ # @params [Array<Integer>] componentIds
5
+ def initialize(componentIds)
6
+ # [Integer(componentId): ComponentColumn]
7
+ @components = componentIds.map do |compId|
8
+ [compId, ComponentColumn.new([])]
9
+ end.to_h
10
+ @availableEntityRows = []
11
+ @entities = []
12
+ end
13
+
14
+ # @return [int]
15
+ def new_arch_row_id(entityId)
16
+ id = @availableEntityRows.pop
17
+ if id != nil
18
+ @entities[id] = entityId
19
+ return id
20
+ else
21
+ id = @entities.size
22
+ @entities.push entityId
23
+ return id
24
+ end
25
+ end
26
+
27
+ def set_component(archRowId, component)
28
+ compCol = @components[component.class.object_id]
29
+ if compCol.components.size <= archRowId
30
+ compCol.components.push component
31
+ else
32
+ compCol.components[archRowId] = component
33
+ end
34
+ end
35
+
36
+ def get_component(archRowId, componentId)
37
+ return @components[componentId].components[archRowId]
38
+ end
39
+
40
+ def has_component(componentId)
41
+ return @components[componentId] != nil
42
+ end
43
+
44
+ def remove_entity(archRowId)
45
+ @availableEntityRows.push(archRowId)
46
+ @entities[archRowId] = nil
47
+ end
48
+
49
+ # @return [[Component, EntityId]]
50
+ def active_components(componentIds)
51
+ # [comp1a, comp1b]
52
+ # [comp2a, comp2b]
53
+ # ...
54
+ components = componentIds
55
+ .map do |compId|
56
+ @components[compId].components
57
+ end
58
+ compCount = componentIds.size
59
+
60
+ @entities
61
+ .each_with_index
62
+ .filter do |entId, _|
63
+ entId != nil
64
+ end
65
+ .map do |_, rowIdx|
66
+ (0...compCount).map do |compRowId|
67
+ components[compRowId][rowIdx]
68
+ end
69
+ end
70
+ end
71
+
72
+ def components_and_ids(componentIds)
73
+ # [comp1a, comp1b]
74
+ # [comp2a, comp2b]
75
+ # ...
76
+ components = componentIds
77
+ .map do |compId|
78
+ @components[compId].components
79
+ end
80
+ compCount = componentIds.size
81
+
82
+ @entities
83
+ .each_with_index
84
+ .filter do |entId, _|
85
+ entId != nil
86
+ end
87
+ .map do |entId, rowIdx|
88
+ [entId, *(0...compCount).map do |compRowId|
89
+ components[compRowId][rowIdx]
90
+ end]
91
+ end
92
+ end
93
+ end
data/lib/bitmap.rb ADDED
@@ -0,0 +1,32 @@
1
+ # TODO: actual bitmap implementation
2
+ class Bitmap
3
+ def initialize
4
+ @flags = []
5
+ end
6
+
7
+ def contains(id)
8
+ return (@flags.size > id) && @flags[id]
9
+ end
10
+
11
+ def set(id)
12
+ if @flags.size < id
13
+ (@flags.size...id).each do |i|
14
+ @flags.push false
15
+ end
16
+ end
17
+
18
+ @flags[id] = true
19
+ end
20
+
21
+ def remove(id)
22
+ if @flags.size > id
23
+ @flags[id] = false
24
+ end
25
+ end
26
+
27
+ def clear
28
+ (0...@flags.size).each do |i|
29
+ @flags[i] = false
30
+ end
31
+ end
32
+ end
data/lib/entity.rb ADDED
@@ -0,0 +1,102 @@
1
+ require 'set'
2
+ require_relative 'bitmap'
3
+
4
+ Entity = Struct.new(:archId, :archRow)
5
+
6
+ class EntityStore
7
+ def initialize
8
+ # [Entity]
9
+ @entities = []
10
+ # [Bitmap]
11
+ @flags = []
12
+ @nextId = 0
13
+ @deadEntities = Set.new
14
+ end
15
+
16
+ # Returns a new entity id (int)
17
+ def new_id
18
+ id = @deadEntities.first
19
+ if id != nil
20
+ @deadEntities.delete id
21
+ return id
22
+ else
23
+ id = @nextId
24
+ @nextId += 1
25
+ return id
26
+ end
27
+ end
28
+
29
+ # Spawn a new entity with the given ids
30
+ def spawn(entityId, archId, archRowId)
31
+ if @entities.count <= entityId
32
+ @entities.push(Entity.new(archId, archRowId))
33
+ else
34
+ @entities[entityId] = Entity.new(archId, archRowId)
35
+ self.reset_flags(entityId)
36
+ end
37
+ end
38
+
39
+ # Returns an entity
40
+ # @param [Integer] entityId
41
+ def get(entityId)
42
+ return @entities[entityId]
43
+ end
44
+
45
+ # def exists?(entityId)
46
+ # return @entities.size > entityId
47
+ # end
48
+
49
+ # Mark an entity as dead
50
+ def kill(entityId)
51
+ @deadEntities.add entityId
52
+ end
53
+
54
+ def entity_count
55
+ return ((0...@entities.count).reduce(0) do |count, id|
56
+ if @deadEntities.include? id
57
+ return count
58
+ else
59
+ return count + 1
60
+ end
61
+ end)
62
+ end
63
+
64
+ # @param [Integer, #read] entityId
65
+ # @param [Integer, #read] flagId
66
+ # @return [bool]
67
+ def has_flag(entityId, flagId)
68
+ if @flags.size <= entityId
69
+ return false
70
+ else
71
+ return @flags[entityId].contains(flagId)
72
+ end
73
+ end
74
+
75
+ def set_flag(entityId, flagId)
76
+ if @flags.size <= entityId
77
+ (@flags.size..entityId).each do |_|
78
+ @flags.push(Bitmap.new)
79
+ end
80
+ end
81
+
82
+ @flags[entityId].set(flagId)
83
+ end
84
+
85
+ def remove_flag(entityId, flagId)
86
+ if @flags.size <= entityId
87
+ return
88
+ end
89
+
90
+ @flags[entityId].remove(flagId)
91
+ end
92
+
93
+ def reset_flags(entityId)
94
+ @flags[entityId].clear()
95
+ end
96
+
97
+ def entity_ids
98
+ return (0...@nextId).filter do |id|
99
+ !@deadEntities.include?(id)
100
+ end
101
+ end
102
+ end
data/lib/kiwi-ecs.rb ADDED
@@ -0,0 +1,20 @@
1
+ # Kiwi ecs
2
+ #
3
+ # An easy-to-use ECS implementation focussed on fast iteration of components
4
+ #
5
+ # Copyright (C) 2023 Jonas Everaert
6
+ #
7
+ # This program is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # This program is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
19
+
20
+ require_relative 'world'
data/lib/query.rb ADDED
@@ -0,0 +1,56 @@
1
+ module Query
2
+ # @return [enumerator<Integer>]
3
+ def query_ids
4
+ if block_given?
5
+ @entityStore.entity_ids.each do |id|
6
+ yield id
7
+ end
8
+ else
9
+ return @entityStore.entity_ids
10
+ end
11
+ end
12
+
13
+ def query(*componentType)
14
+ componentIds = componentType.map { |type| type.object_id }
15
+
16
+ result = @archStore.compMap
17
+ .filter do |archComponents, _|
18
+ (componentIds - archComponents).empty?
19
+ end
20
+ .flat_map do |_, archId|
21
+ @archStore
22
+ .get(archId)
23
+ .active_components(componentIds)
24
+ end
25
+
26
+ if block_given?
27
+ result.each do |a|
28
+ yield a
29
+ end
30
+ else
31
+ return result.to_enum
32
+ end
33
+ end
34
+
35
+ def query_with_ids(*componentType)
36
+ componentIds = componentType.map { |type| type.object_id }
37
+
38
+ result = @archStore.compMap
39
+ .filter do |archComponents, _|
40
+ (componentIds - archComponents).empty?
41
+ end
42
+ .flat_map do |_, archId|
43
+ @archStore
44
+ .get(archId)
45
+ .components_and_ids(componentIds)
46
+ end
47
+
48
+ if block_given?
49
+ result.each do |a|
50
+ yield a
51
+ end
52
+ else
53
+ return result.to_enum
54
+ end
55
+ end
56
+ end
data/lib/world.rb ADDED
@@ -0,0 +1,64 @@
1
+ require_relative 'entity'
2
+ require_relative 'arch_store'
3
+ require_relative 'query'
4
+
5
+ class World
6
+ def initialize
7
+ @entityStore = EntityStore.new
8
+ @archStore = ArchStore.new
9
+ end
10
+
11
+ def spawn(*components)
12
+ compIds = components.map { |c| c.class.object_id }.sort
13
+ archId = @archStore.get_archetype_id(compIds)
14
+ entId = @entityStore.new_id
15
+ archetype = @archStore.get(archId)
16
+ archRowId = archetype.new_arch_row_id(entId)
17
+ @entityStore.spawn(entId, archId, archRowId)
18
+ for component in components
19
+ archetype.set_component(archRowId, component)
20
+ end
21
+ return entId
22
+ end
23
+
24
+ def get_component(entityId, componentType)
25
+ entity = @entityStore.get(entityId)
26
+ archetype = @archStore.get(entity.archId)
27
+ archetype.get_component(entity.archRow, componentType.object_id)
28
+ end
29
+
30
+ def kill(entityId)
31
+ entity = @entityStore.get entityId
32
+ @entityStore.kill(entityId)
33
+ archetype = @archStore.get(entity.archId)
34
+ archetype.remove_entity(entity.archRow)
35
+ end
36
+
37
+ def entity_count
38
+ return @entityStore.entity_count
39
+ end
40
+
41
+ def has_component(entityId, componentType)
42
+ entity = @entityStore.get(entityId)
43
+ archetype = @archStore.get(entity.archId)
44
+ return archetype.has_component(componentType.object_id)
45
+ end
46
+
47
+ def has_flag(entityId, flagId)
48
+ return @entityStore.has_flag entityId, flagId
49
+ end
50
+
51
+ def has_flags(entityId, *flagIds)
52
+ return flagIds.filter { |flagId| !@entityStore.has_flag entityId, flagId }.count == 0
53
+ end
54
+
55
+ def set_flag(entityId, flagId)
56
+ @entityStore.set_flag(entityId, flagId)
57
+ end
58
+
59
+ def remove_flag(entityId, flagId)
60
+ @entityStore.remove_flag(entityId, flagId)
61
+ end
62
+
63
+ include Query
64
+ end
metadata ADDED
@@ -0,0 +1,186 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kiwi-ecs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Jonas Everaert
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-08-11 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |
14
+ # Kiwi
15
+
16
+ Kiwi is a versatile entity component system focussing on fast iteration and a nice api.
17
+
18
+ To get started, read the [usage guide](#usage) below.
19
+
20
+ [![Tests](https://github.com/Jomy10/kiwi-ecs-ruby/actions/workflows/tests.yml/badge.svg)](https://github.com/Jomy10/kiwi-ecs-ruby/actions/workflows/tests.yml)
21
+
22
+ ## Installation
23
+
24
+ This library is currently not on ruby gems, but will arrive shortly.
25
+
26
+ To use it now, simple `git clone https://github.com/jomy10/kiwi-ecs-ruby`.
27
+
28
+ Then incude the world.rb file in your ruby files like so:
29
+
30
+ ```ruby
31
+ require_relative 'kiwi-ecs-ruby/src/world.rb'
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ ### The world
37
+
38
+ The world is the main object that controls the ecs.
39
+
40
+ ```ruby
41
+ world = World.new
42
+ ```
43
+
44
+ ### Components
45
+
46
+ Creating a component is as simple as declaring a struct:
47
+
48
+ ```ruby
49
+ Position = Struct.new :x, :y
50
+ ```
51
+
52
+ Classes can also be used instead of structs
53
+
54
+ ```ruby
55
+ class Velocity
56
+ attr_accessor :x
57
+ attr_accessor :y
58
+ end
59
+ ```
60
+
61
+ ### Entities
62
+
63
+ An entity is spawned with a set of components:
64
+
65
+ ```ruby
66
+ entityId = world.spawn(Position.new(10, 10))
67
+
68
+ world.spawn(Position.new(3, 5), Velocity.new(1.5, 0.0))
69
+ ```
70
+
71
+ The `world.spawn(*components)` function will return the id of the spawned entity.
72
+
73
+ Killing an entity can be done using `world.kill(entityId)`:
74
+
75
+ ```ruby
76
+ world.kill(entityId)
77
+ ```
78
+
79
+ ### Systems
80
+
81
+ #### Queries
82
+
83
+ Queries can be constructed as follows:
84
+
85
+ ```ruby
86
+ # Query all position componentss
87
+ world.query(Position) do |pos|
88
+ puts pos
89
+ end
90
+
91
+ # Query all entities having a position and a velocity component, and their entity ids
92
+ world.query_with_ids(Position, Velocity) do |id, pos, vel|
93
+ # ...
94
+ end
95
+ ```
96
+
97
+ ### Flags
98
+
99
+ Entities can be tagged using flags
100
+
101
+ #### Defining flags
102
+
103
+ A flag is an integer
104
+
105
+ ```ruby
106
+ module Flags
107
+ Player = 0
108
+ Enemy = 1
109
+ end
110
+ ```
111
+
112
+ #### Setting flags
113
+
114
+ ```ruby
115
+ id = world.spawn
116
+
117
+ world.set_flag(id, Flags::Player)
118
+ ```
119
+
120
+ #### Removing a flag
121
+
122
+ ```ruby
123
+ world.remove_flag(id, Flags::Player)
124
+ ```
125
+
126
+ #### Checking wether an entity has a flag
127
+
128
+ ```ruby
129
+ world.has_flag(id, Flags::Player)
130
+ ```
131
+
132
+ #### Filtering queries with flags
133
+
134
+ ```ruby
135
+ # TODO
136
+ ```
137
+
138
+ The `hasFlags` function is also available for when you want to check multiple flags.
139
+
140
+ ## Road map
141
+
142
+ - [ ] System groups
143
+
144
+ ## Contributing
145
+
146
+ Contributors are welcome to open an issue requesting new features or fixes or opening a pull request for them.
147
+
148
+ ## License
149
+
150
+ The library is licensed under LGPLv3.
151
+ email:
152
+ executables: []
153
+ extensions: []
154
+ extra_rdoc_files: []
155
+ files:
156
+ - lib/arch_store.rb
157
+ - lib/archetype.rb
158
+ - lib/bitmap.rb
159
+ - lib/entity.rb
160
+ - lib/kiwi-ecs.rb
161
+ - lib/query.rb
162
+ - lib/world.rb
163
+ homepage: https://github.com/jomy10/kiwi-ecs-ruby
164
+ licenses:
165
+ - LGPL-3.0-or-later
166
+ metadata: {}
167
+ post_install_message:
168
+ rdoc_options: []
169
+ require_paths:
170
+ - lib
171
+ required_ruby_version: !ruby/object:Gem::Requirement
172
+ requirements:
173
+ - - ">="
174
+ - !ruby/object:Gem::Version
175
+ version: '0'
176
+ required_rubygems_version: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ requirements: []
182
+ rubygems_version: 3.3.3
183
+ signing_key:
184
+ specification_version: 4
185
+ summary: An entity component system with a nice api, fit for a variety of use cases
186
+ test_files: []