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
|
#
# lkn.core: an entity-component-system (ecs) implementation for lyxan
# Copyright (C) 2017 Thomas Letan <contact@thomasletan.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
defmodule Lkn.Core.Entity.Components do
use Supervisor
alias Lkn.Core.Entity
alias Lkn.Core.System
@moduledoc false
@spec start_link([module], Entity.k) :: Supervisor.on_start
def start_link(comps, entity_key) do
Supervisor.start_link(__MODULE__, {comps, entity_key})
end
@spec init({[System.m], Entity.k}) ::
{:ok, {:supervisor.sup_flags, [Supervisor.Spec.spec]}} |
:ignore
def init({comps, entity_key}) do
children = Enum.map(comps, fn comp_module ->
worker(comp_module, [entity_key])
end)
supervise(children, strategy: :one_for_one)
end
end
defmodule Lkn.Core.Entity do
use Lkn.Prelude
alias Lkn.Core.Name
alias Lkn.Core.Properties
@typedoc """
A property of the Entity, e.g. its health point, its current speed, etc.
"""
@type prop :: any
@typedoc """
A value associated to a given Entity's property.
"""
@type value :: any
@moduledoc """
A behaviour module for implementing an Entity.
**Note:** An Entity can either be a Map or a Puppet. To actually
implement an Entity, you should use either
`Lkn.Core.Puppet.defpuppet/2` or `Lkn.Core.Map.defmap/2`. In other
words, if your code contains `@behaviour Lkn.Core.Entity`, you are
doing it wrong. From a developer point of view, only the
`start_link/3` function is really useful.
"""
@typedoc """
A key to identify and reach an Entity, that is either a
`Lkn.Core.Puppet` or a `Lkn.Core.Map`.
"""
@type k :: Lkn.Core.Map.k | Lkn.Core.Puppet.k
@type digest :: term
@typedoc """
The third argument of the `start_link/3` function which is passed to
the `init_properties/1` callback.
"""
@type init_args :: any
@doc """
Initializes the Entity's map of properties.
This map is used by
"""
@callback init_properties(init_args) :: %{prop => value}
@callback digest(entity :: %{prop => value}) :: digest
defmacro __using__(components: comps) do
quote location: :keep do
use Supervisor
@behaviour Lkn.Core.Entity
def init(key: entity_key, args: args) do
props = init_properties(args)
props = Map.put(props, :make_digest, & __MODULE__.digest(&1))
sys = Enum.map(unquote(comps), &(&1.specs().system()))
children = [
worker(Agent, [fn -> sys end, [name: Name.comps_list(entity_key)]]),
worker(Properties, [props, entity_key]),
supervisor(Lkn.Core.Entity.Components, [unquote(comps), entity_key]),
]
supervise(children, strategy: :rest_for_one)
end
end
end
@doc """
Compute a digest which hopefully describes the entity
"""
@spec digest(entity_key :: k) :: digest
def digest(entity_key) do
Option.some(di) = Lkn.Core.Entity.read(entity_key, :make_digest)
Lkn.Core.Properties.compute(entity_key, di)
end
@doc """
Starts an Entity process linked to the current process.
"""
@spec start_link(module, k, init_args) :: Supervisor.on_start
def start_link(module, key, args) do
Supervisor.start_link(module, [key: key, args: args], name: Name.entity(key))
end
@doc false
@spec has_component?(k, System.m) :: boolean
def has_component?(key, sys) do
Agent.get(Lkn.Core.Name.comps_list(key), &Enum.member?(&1, sys))
end
@doc false
@spec systems(k) :: [System.m]
def systems(key) do
Agent.get(Lkn.Core.Name.comps_list(key), fn r -> r end)
end
@doc """
Retreive the current value of the given property, if it exists.
There is no `write` counterpart, because only a `Component` can
modify it.
"""
@spec read(k, prop) :: Option.t(value)
def read(key, prop) do
Lkn.Core.Properties.read(key, prop)
end
end
|