kiwi-ecs 0.0.2

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.
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: []