Skip to content

Commit

Permalink
added everything and made all tests
Browse files Browse the repository at this point in the history
  • Loading branch information
louisponet committed Oct 19, 2019
1 parent abb9a35 commit b8b2b79
Show file tree
Hide file tree
Showing 12 changed files with 936 additions and 11 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

Manifest\.toml
4 changes: 4 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ name = "ECS"
uuid = "1ada24be-c16d-4464-9f61-27c2e0f16645"
authors = ["louisponet <[email protected]>"]
version = "0.1.0"

[deps]
Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
18 changes: 18 additions & 0 deletions src/ECS.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
module ECS
using Parameters

abstract type ComponentData end

abstract type AbstractComponent{T<:ComponentData} end

abstract type System end

abstract type AbstractManager end

include("indices.jl")
include("entity.jl")
include("component.jl")
include("system.jl")
include("manager.jl")

export AbstractManager, Manager, System, SystemStage, Component, SharedComponent, Entity
export @component, @shared_component, @component_with_kw, @shared_component_with_kw
export @entities_in

export update_systems, schedule_delete!, delete_scheduled!

end # module
266 changes: 266 additions & 0 deletions src/component.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
"Can be used to specify the type of component storage to be used for a given `ComponentData`."
component_type(::Type{<:ComponentData}) = Component

# Used together with the indexing scheme to put stubs in
# component arrays that lack the components for a particular index.
struct Stub <: ComponentData end


"""
The most basic Component type.
Indexing into a component with an `Entity` will return the data linked to that entity,
indexing with a regular `Int` will return directly the `ComponentData` that is stored in the data
vector at that index, i.e. generally not the storage linked to the `Entity` with that `Int` as id.
"""
struct Component{T<:ComponentData} <: AbstractComponent{T}
indices::Indices
data::Vector{T}
end

Component{T}() where {T} = Component(Indices(), T[])


const EMPTY_COMPONENT = Component{Stub}()

"""
A shared component works very much like a normal component except that it tries to not have duplicate
data for different entities. This should be used for very large `ComponentData`.
"""
struct SharedComponent{T<:ComponentData} <: AbstractComponent{T}
indices::Indices
data::Vector{Int} #saves the indices into the sharedfor each of the entities
shared::Vector{T}
end

SharedComponent{T}() where {T<:ComponentData} = SharedComponent{T}(Indices(), Int[], T[])

##### BASE Extensions ####
Base.eltype(::Type{<:AbstractComponent{T}}) where T = T

Base.length(c::AbstractComponent) = length(c.data)

Base.in(i::Integer, c::AbstractComponent) = in(i, c.indices)
Base.in(e::Entity, c::AbstractComponent) = in(e.id, c)

Base.isempty(c::AbstractComponent) = isempty(c.data)

function Base.delete!(c::AbstractComponent, es::Vector{Entity})
for e in es
if e in c
pop!(c, e)
end
end
end

Base.getindex(c::Component, e::Entity) = c.data[c.indices[e.id]]
Base.getindex(c::SharedComponent, e::Entity) = c.shared[c.data[c.indices[e.id]]]
Base.getindex(c::Component, i::Integer) = c.data[i]
Base.getindex(c::SharedComponent, i::Integer) = c.shared[i]

function Base.setindex!(c::Component{T}, v::T, e::Entity) where {T}
eid = e.id
if !in(e, c)
push!(c.indices, eid)
push!(c.data, v)
else
@inbounds c.data[c.indices[eid]] = v
end
return v
end
function Base.setindex!(c::SharedComponent{T}, v::T, e::Entity) where {T}
eid = e.id
t_shared_id = findfirst(x -> x==v, c.shared)
shared_id = t_shared_id === nothing ? (push!(c.shared, v); length(c.shared)) : t_shared_id
if !in(e, c)
push!(c.indices, eid)
push!(c.data, shared_id)
else
@inbounds c.data[c.indices[eid]] = shared_id
end
return v
end

function Base.empty!(c::Component)
empty!(c.indices)
empty!(c.data)
return c
end
function Base.empty!(c::SharedComponent)
empty!(c.indices)
empty!(c.data)
empty!(c.shared)
return c
end

function pop_indices_data!(c::AbstractComponent, e::Entity)
@boundscheck if !in(e, c)
throw(BoundsError(c, e))
end
n = length(c)
@inbounds begin
id = c.indices[e.id]
v = c.data[id]
c.data[id] = c.data[end]
pop!(c.data)
pop!(c.indices, e.id)
return v
end
end

Base.pop!(c::Component, e::Entity) = pop_indices_data!(c, e)

function Base.pop!(c::SharedComponent, e::Entity)
i = pop_indices_data!(c, e)
idvec = c.data
val = c.shared[i]
if !any(isequal(i), idvec)
for j in 1:length(idvec)
if idvec[j] > i
idvec[j] -= 1
end
end
deleteat!(c.shared, i)
end
return val
end


########################################
# #
# Iteration #
# #
########################################

struct EntityIterator{T<:IndicesIterator}
it::T
end

Base.length(i::EntityIterator) = length(i.it)

@inline function Base.iterate(i::EntityIterator, state=1)
n = iterate(i.it, state)
n === nothing && return n
return Entity(n[1]), n[2]
end

macro entities_in(indices_expr)
expr, t_sets, t_orsets = expand_indices_bool(indices_expr)
return esc(quote
t_comps = $(Expr(:tuple, t_sets...))
t_or_comps = $(Expr(:tuple, t_orsets...))
sets = map(x -> x.indices, t_comps)
orsets = map(x -> x.indices, t_or_comps)
if isempty(sets)
minlen, minid = findmin(map(length, orsets))
t_shortest = orsets[minid]
else
minlen, minid = findmin(map(length, sets))
t_shortest = sets[minid]
end
if $(!isempty(t_orsets))
shortest = deepcopy(t_shortest)
for s in orsets
union!(shortest, s)
end
else
shortest = t_shortest
end
ECS.EntityIterator(ECS.IndicesIterator(shortest, x -> $expr, length(shortest)))
end)
end

########################################
# #
# ComponentData indexing scheme #
# #
########################################

const COMPONENTDATA_TYPES = Symbol[]

component_id(::Type{<:ComponentData}) = -1

function typename(typedef::Expr)
if typedef.args[2] isa Symbol
return typedef.args[2]
elseif typedef.args[2].args[1] isa Symbol
return typedef.args[2].args[1]
elseif typedef.args[2].args[1].args[1] isa Symbol
return typedef.args[2].args[1].args[1]
else
error("Could not parse type-head from: $typedef")
end
end

function process_typedef(typedef, mod, with_kw=false)
tn = ECS.typename(typedef)
ctypes = COMPONENTDATA_TYPES
if !(tn in ctypes)
push!(ctypes, tn)
if typedef.args[2] isa Symbol
typedef.args[2] = Expr(Symbol("<:"), tn, ECS.ComponentData)
elseif typedef.args[2].head == Symbol("<:")
if !Base.eval(mod, :($(typedef.args[2].args[2]) <: ECS.ComponentData))
error("Components can only have supertypes which are subtypes of ComponentData.")
end
else
error("Components can not have type parameters")
# typ_pars = typedef.args[2].args[2:end]
# typedef.args[2] = Expr(Symbol("<:"), Expr(:curly, tn, typ_pars...), :ComponentData)
end
id = length(COMPONENTDATA_TYPES)
if with_kw
tq = quote
ECS.Parameters.@with_kw $typedef
ECS.component_id(::Type{$tn}) = $id
end
else
tq = quote
$typedef
ECS.component_id(::Type{$tn}) = $id
end
end
return tq, tn
end
end

macro component(typedef)
return esc(ECS._component(typedef, __module__))
end
macro component_with_kw(typedef)
return esc(ECS._component(typedef, __module__, true))
end

function _component(typedef, mod::Module, args...)
t = process_typedef(typedef, mod, args...)
if t !== nothing
t1, tn = t
return quote
$t1
ECS.component_type(::Type{$tn}) = ECS.Component
end
end
end

macro shared_component(typedef)
return esc(ECS._shared_component(typedef, __module__))
end

macro shared_component_with_kw(typedef)
return esc(ECS._shared_component(typedef, __module__, true))
end

function _shared_component(typedef, mod::Module, args...)
t = process_typedef(typedef, mod, args...)
if t !== nothing
t1, tn = t
return quote
$t1
ECS.component_type(::Type{$tn}) = ECS.SharedComponent
end
end
end




26 changes: 26 additions & 0 deletions src/entity.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
struct Entity
id::Int
end

function Entity(m::AbstractManager)
if !isempty(free_entities(m))
e = pop!(free_entities(m))
entities(m)[e.id] = e
return e
end
n = length(entities(m)) + 1
e = Entity(n)
push!(entities(m), e)
return e
end

function Entity(m::AbstractManager, datas::ComponentData...)
e = Entity(m)
for d in datas
m[e] = d
end
return e
end

const EMPTY_ENTITY = Entity(0)

Loading

0 comments on commit b8b2b79

Please sign in to comment.