summaryrefslogtreecommitdiff
path: root/honey/ecs.lua
blob: 8aa5c5cff45dcbfd38c8520c3ff100cf17abf307 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
math.randomseed(os.time())

local module = {}
setmetatable(module, {__index=_G})
setfenv(1, module)


--===== Components =====--

-- Components provide a kind-of-type-safe way of storing values, since they are
-- guaranteed to have a specific set of keys and no other values in them. They also serialize
-- nicely for storage.

Component = {}

function Component.newFactory(name, params, serialize)
	-- create the metatable for this component type
	local metatable = {}
	metatable.__tostring = serialize or Component.serialize -- nice serialization
	metatable.__newindex = function(tbl, index, value)
		if params[index] ~= nil then
			tbl[index] = value
		else
			-- throw an error if we try to set a key that is not in the valid set
			error(string.format("%q is not a valid key for a component of type %q", index, name))
		end
	end

	-- the factory function for creating components
	return function(tbl)
		local tbl = tbl or {}
		local self = { __type=name }

		-- ensure that all keys are present
		for k, v in pairs(params) do
			self[k] = v
		end
		setmetatable(self, metatable)

		-- set the keys from the factory call
		for k, v in pairs(tbl) do
			self[k] = v
		end

		return self
	end
end


function Component.serialize(self)
	local str = "{"
	for k, v in pairs(self) do
		if type(v) == "string" then
			str = str .. string.format("%s=%q,", k, v)
		else
			str = str .. string.format("%s=%s,", k, tostring(v))
		end
	end
	str = string.sub(str, 1, -2) .. "}"
	return str
end


--===== EntityDb =====--


-- EntityDb is a database of entities and their associated components
-- it should be quite efficient to query for all entities with a given component, and reasonably
-- efficient to query for all components of a given entity


EntityDb = {}
EntityDb.__index = EntityDb


function EntityDb.new(_)
	local self = {
		entities = {},
		components = {},
	}
	setmetatable(self, EntityDb)
	return self
end
setmetatable(EntityDb, {__call=EntityDb.new})


function EntityDb.checkIsValid(self, id)
	if not self.entities[id] then
		error(string.format("invalid entity id: %s", tostring(id)))
	end
end


local function uid()
	local template ='xxxx:xxxx:xxxx'
	return string.gsub(template, 'x', function (c)
		local v = math.random(0, 0xf)
		return string.format('%x', v)
	end)
end
function EntityDb.createEntity(self)
	local id = uid()
	self.entities[id] = true
	return id
end


function EntityDb.addComponent(self, id, name, value)
	self:checkIsValid(id)
	if not self.components[name] then
		self.components[name] = { count=0 }
	end

	local component = self.components[name]
	component[id] = value
	component.count = component.count + 1
end


local function shallowCopy(tbl)
	local copy = {}
	for k, v in pairs(tbl) do copy[k] = v end
	return copy
end


function EntityDb.queryComponent(self, name)
	local query = shallowCopy(self.components[name])
	query.count = nil
	return query
end


function EntityDb.queryEntity(self, id)
	self:checkIsValid(id)
	local query = {}
	for name, component in pairs(self.components) do
		query[name] = component[id]
	end
	return query
end


function EntityDb.removeComponent(self, id, name)
	self:checkIsValid(id)
	local component = self.components[name]
	if component[id] ~= nil then
		component[id] = nil
		component.count = component.count - 1
		if component.count == 0 then
			self.components[name] = nil
		end
	end
end


function EntityDb.deleteEntity(self, id)
	self:checkIsValid(id)
	for name in pairs(self.components) do
		self:removeComponent(id, name)
	end
	self.entities[id] = nil
end

return module