Skip to content

Commit

Permalink
🚧 Namespace properties as system.a.b.c. 🚧
Browse files Browse the repository at this point in the history
  • Loading branch information
iago-lito committed Sep 11, 2024
1 parent 6dfcd77 commit ae7357b
Show file tree
Hide file tree
Showing 9 changed files with 398 additions and 276 deletions.
3 changes: 2 additions & 1 deletion src/Framework/Framework.jl
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ module Framework
# - [x] Blueprints 'history' become meaningless if methods can mutate the internal state.
# - [x] Recurring pattern: various blueprints types provide 'the same component': reify.
# - [x] `depends(other_method_name)` to inherit all dependent components.
# - [ ] Namespace properties into like system.namespace.namespace.property.
# - [.] Namespace properties into like system.namespace.namespace.property.
# - [ ] Hooks need to trigger when special components combination become available.
# See for instance the expansion of `Nutrients.Nodes`
# which should trigger the creation of links if there is already `Species`.. or vice
Expand Down Expand Up @@ -133,6 +133,7 @@ include("./system.jl") # <- Defines the 'System' type used in the next files..
include("./component.jl") # <- But better start reading from this file.
include("./blueprints.jl")
include("./methods.jl")
include("./properties.jl")
include("./add.jl")
include("./plus_operator.jl")

Expand Down
12 changes: 6 additions & 6 deletions src/Framework/method_macro.jl
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ function method_macro(__module__, __source__, input...)

for rdep in raw_deps
subdeps = if dep isa Function
depends(ValueType, typeof(rdep))
depends(System{ValueType}, typeof(rdep))
else
[rdep]
end
Expand Down Expand Up @@ -376,19 +376,19 @@ function method_macro(__module__, __source__, input...)
if kw == read_kw
quote
for psymbol in $[propsymbols...]
has_read_property(ValueType, Val(psymbol)) &&
has_read_property(System{ValueType}, Val(psymbol)) &&
xerr("The property :$psymbol is already defined \
for systems of '$ValueType'.")
end
end
else
quote
for psymbol in $[propsymbols...]
has_read_property(ValueType, Val(psymbol)) ||
has_read_property(System{ValueType}, Val(psymbol)) ||
xerr("The property :$psymbol cannot be marked 'write' \
without having first been marked 'read' \
for systems of '$ValueType'.")
has_write_property(ValueType, Val(psymbol)) &&
has_write_property(System{ValueType}, Val(psymbol)) &&
xerr("The property :$psymbol is already marked 'write' \
for systems of '$ValueType'.")
end
Expand All @@ -406,7 +406,7 @@ function method_macro(__module__, __source__, input...)

# Generate dependencies method.
push_res!(quote
Framework.depends(::Type{ValueType}, ::Type{typeof(fn)}) = deps
Framework.depends(::Type{System{ValueType}}, ::Type{typeof(fn)}) = deps
end)

# Wrap the detected methods within checked methods receiving 'System' values.
Expand Down Expand Up @@ -473,7 +473,7 @@ function method_macro(__module__, __source__, input...)
set = (proptype == read_kw) ? :set_read_property! : :set_write_property!
push_res!(quote
for psymbol in $[propsymbols...]
$set(ValueType, psymbol, fn)
$set(System{ValueType}, psymbol, fn)
end
end)
end
Expand Down
98 changes: 4 additions & 94 deletions src/Framework/methods.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,12 @@
# Only the second one needs to be specified by the framework user,
# and then the @method macro should do the rest (see documentation there).
#
# Methods with these exact signatures:
#
# - method(v::Value)
# - method!(v::Value, rhs)
#
# .. can optionally become properties of the system/value,
# in the sense of julia's `getproperty/set_property!`.
#
# The properties also come in two styles:
#
# - system.property -> Checks that the required components exist.
# - value.property -> Runs and see what happens.
#
# The polymorphism of methods use julia dispatch over function types.

# Methods depend on nothing by default.
depends(::Type{V}, ::Type{<:Function}) where {V} = CompType{V}[]
depends(::Type{System{V}}, ::Type{<:Function}) where {V} = CompType{V}[]
missing_dependencies_for(fn::Type{<:Function}, s::System{V}) where {V} =
Iterators.filter(depends(V, fn)) do dep
Iterators.filter(depends(System{V}, fn)) do dep
!has_component(s, dep)
end
# Just pick the first one. Return nothing if dependencies are met.
Expand All @@ -41,80 +28,15 @@ function first_missing_dependency_for(fn::Type{<:Function}, s::System)
end

# Direct call with the functions themselves.
depends(::Type{V}, fn::Function) where {V} = depends(V, typeof(fn))
depends(T::Type, fn::Function) = depends(T, typeof(fn))
missing_dependencies_for(fn::Function, s::System) = missing_dependencies_for(typeof(fn), s)
first_missing_dependency_for(fn::Function, s::System) =
first_missing_dependency_for(typeof(fn), s)

# Map wrapped system value and property name to the corresponding function.
read_property(V::Type, ::Val{name}) where {name} =
throw(PropertyError(V, name, "Unknown property."))
write_property(V::Type, ::Val{name}) where {name} =
throw(PropertyError(V, name, "Unknown property."))

has_read_property(V::Type, n::Val{name}) where {name} =
try
read_property(V, n)
true
catch e
e isa PropertyError || rethrow(e)
false
end

possible_write_property(V::Type, n::Val{name}) where {name} =
try
write_property(V, n)
catch e
e isa PropertyError || rethrow(e)
nothing
end

has_write_property(V::Type, n::Val{name}) where {name} =
!isnothing(possible_write_property(V, n))

function readwrite_property(V::Type, n::Val{name}) where {name}
read_property(V, n) # Errors if not even 'read-'.
try
write_property(V, n)
catch e
e isa PropertyError || rethrow(e)
properr(V, name, "This property is read-only.")
end
end

# Hack flag to avoid that the checks below interrupt the `Revise` process.
# Raise when done defining properties in the package.
# Raise when done defining methods in the package.
global REVISING = false

# Set read property first..
function set_read_property!(V::Type, name::Symbol, fn::Function)
REVISING ||
has_read_property(V, Val(name)) &&
properr(V, name, "Readable property already exists.")
# Dynamically add method to connect property name to the given function.
name = Meta.quot(name)
eval(quote
read_property(::Type{$V}, ::Val{$name}) = $fn
end)
end

# .. and only after, and optionally, the corresponding write property.
function set_write_property!(V::Type, name::Symbol, fn::Function)
has_read_property(V, Val(name)) || properr(
V,
name,
"Property cannot be set writable \
without having been set readable first.",
)
REVISING ||
has_write_property(V, Val(name)) &&
properr(V, name, "Writable property already exists.")
name = Meta.quot(name)
eval(quote
write_property(::Type{$V}, ::Val{$name}) = $fn
end)
end

# ==========================================================================================
# Dedicated exceptions.

Expand All @@ -129,15 +51,3 @@ function Base.showerror(io::IO, e::MethodError{V}) where {V}
println(io, "In method '$(e.name)' for '$V': $(e.message)")
end
metherr(V, n, m) = throw(MethodError(V, n, m))

# About properties use.
struct PropertyError{V} <: SystemException
name::Symbol
message::String
_::PhantomData{V}
PropertyError(::Type{V}, s, m) where {V} = new{V}(s, m, PhantomData{V}())
end
function Base.showerror(io::IO, e::PropertyError{V}) where {V}
println(io, "In property :$(e.name) of '$V': $(e.message)")
end
properr(V, n, m) = throw(PropertyError(V, n, m))
Loading

0 comments on commit ae7357b

Please sign in to comment.