Select Git revision
controllers.jl
controllers.jl 14.52 KiB
struct ControllerHub
controllers::Dict{Symbol, Any}
end
ControllerHub() = ControllerHub(Dict{Symbol, Any}())
Base.isempty(hub::ControllerHub) = isempty(hub.controllers)
Base.getindex(hub::ControllerHub, ref::Symbol) = hub.controllers[ref]
Base.setindex!(hub::ControllerHub, val, ref::Symbol) = (hub.controllers[ref] = val)
Base.haskey(hub::ControllerHub, ref::Symbol) = haskey(hub.controllers, ref)
Base.get!(hub::ControllerHub, ref::Symbol, val) = get!(hub.controllers, ref, val)
Base.get(hub::ControllerHub, ref::Symbol, val) = get(hub.controllers, ref, val)
function TidyObservables.newcontroller(main::ControllerHub, key::Symbol, child, args...)
controller = child(main, args...)
main[key] = controller
return controller
end
gethub(c) = c.hub
gethub(hub::ControllerHub) = hub
# events - definitions
const NoLarva = typemax(LarvaID)
struct LarvaEvents
hovered::Observable{LarvaID}
activated::Observable{LarvaID}
deactivated::Observable{LarvaID}
end
LarvaEvents() = LarvaEvents(Observable(NoLarva), Observable(NoLarva), Observable(NoLarva))
larvaevents(c) = get!(gethub(c), :larvaevents, LarvaEvents())
const UserTag = String
const NoTag = ""
struct TagEvents
created::Observable{UserTag}
renamed::Observable{Pair{UserTag, UserTag}}
renamefailed::Observable{Pair{UserTag, UserTag}}
activated::Observable{UserTag}
deactivated::Observable{UserTag}
end
TagEvents() = TagEvents(Observable(NoTag), Observable(NoTag => NoTag), Observable(NoTag => NoTag), Observable(NoTag), Observable(NoTag))
tagevents(c) = get!(gethub(c), :tagevents, TagEvents())
struct TaggingEvents
assignmentfailed::Observable{String}
end
TaggingEvents() = TaggingEvents(Observable(""))
taggingevents(c) = get!(gethub(c), :taggingevents, TaggingEvents())
struct FilterEvents
discard_larva_edits::Observable{LarvaID}
allow_multiple_tags::Observable{Bool}
end
FilterEvents() = FilterEvents(Observable{LarvaID}(0), Observable(false))
function filterevents(controller)
events = get!(gethub(controller), :filterevents, FilterEvents())
if isempty(Observables.listeners(events.discard_larva_edits))
on(events.discard_larva_edits) do id
@assert 0 < id
larva = getlarva(controller, id)
empty!(larva.usertags[])
end
end
return events
end
# events - implementations
function createtag!(controller, tag::ObservableTag)
tagname = tag.name[]
if isempty(tagname)
# tag name was undefined; it is still undefined => do nothing
@warn "empty tag name"
return false
elseif exists(tag, controller)
failure = tagname
# invalidate
tag.name[] = NoTag
renamefailed!(controller, tag, failure)
return false
else
@info "Tag \"$(tagname)\" created"
push!(transactions(controller), CreateTag(tag))
tagevents(controller).created[] = tag
@info "Tag \"$(tagname)\" activated"
tagevents(controller).activated[] = tag
end
return true
end
function renametag!(controller, tag::ObservableTag, newtag::Union{Nothing, UserTag}=nothing)
tagname = tag.name[]
newtag = @something newtag tagname
if isempty(newtag)
@warn "empty tag name"
return false
elseif newtag != tagname
if exists(tag, controller)
renamefailed!(controller, tag, newtag)
return false
else
@info "Tag \"$(tagname)\" renamed \"$(newtag)\""
transaction = tagname => newtag
push!(transactions(controller), RenameTag(transaction))
tagevents(controller).renamed[] = transaction
end
end
return true
end
function renamefailed!(controller,
current::ObservableTag,
failure::UserTag,
)
tagname = current.name[]
@info "Renaming tag \"$(tagname)\" failed"
tagevents(controller).renamefailed[] = tagname => failure
Observables.notify(gettags(controller))
end
function activatetag!(controller, tag::ObservableTag)
tag.active[] && return false
tagname = tag.name[]
@info "Tag \"$(tagname)\" activated"
push!(transactions(controller), ActivateTag(tagname))
tagevents(controller).activated[] = tagname
tag.active[] = true
end
function deactivatetag!(controller, tag::ObservableTag)
tag.active[] || return false
tagname = tag.name[]
@info "Tag \"$(tagname)\" deactivated"
push!(transactions(controller), DeactivateTag(tagname))
tagevents(controller).deactivated[] = tagname
tag.active[] = false
return true
end
function exists(tagname::String, tagsource, lut)
# note: source observable `tagsource` already contains the `tagname` value;
# the present function is called to determine whether to revert `tagsource`
# to its previous value (not passed to this function)
for tag in Observables.to_value(lut)
if tag !== tagsource && tag.name[] == tagname
return true
end
end
return false
end
function assignmentfailed!(controller, reason)
taggingevents(controller).assignmentfailed[] = reason
end
struct LarvaController
hub::ControllerHub
model::Dict{<:Integer, LarvaModel}
tag_lut::AbstractObservable{<:TagLUT}
activelarva::AbstractObservable{<:ActiveLarva}
player::AbstractAnimator
# could make LarvaController mutable instead...
boundingbox::Ref{<:NTuple{4, <:AbstractFloat}}
end
function LarvaController(main::ControllerHub,
model::Dict{<:Integer, LarvaModel},
tag_lut::AbstractObservable{<:TagLUT},
player::AbstractAnimator)
activelarva = Observable{ActiveLarva}(nothing)
boundingbox = Ref((0.0, 10.0, 0.0, 10.0))
LarvaController(main, model, tag_lut, activelarva, player, boundingbox)
end
function LarvaController(main::ControllerHub,
model::Dict{<:Integer, LarvaModel},
tag_lut::TagLUT,
times::Vector{PlanarLarvae.Time})
player = playcontroller(times)
LarvaController(main, model, Observable(tag_lut), player)
end
function LarvaController(main::ControllerHub, model::Vector{LarvaModel}, args...)
model′ = Dict{LarvaID, LarvaModel}()
for larva in model
model′[larva.id] = larva
end
LarvaController(main, model′, args...)
end
getactivelarva(c) = getactivelarva(gethub(c)[:larva])
getactivelarva(c::LarvaController) = c.activelarva
activatelarva!(c, id) = activatelarva!(gethub(c)[:larva], id)
deactivatelarva!(c) = deactivatelarva!(gethub(c)[:larva])
getlarvae(c) = gethub(c)[:larva].model
getlarva(c, id) = getlarvae(c)[id]
setboundingbox!(c, bb) = setboundingbox!(gethub(c)[:larva], bb)
setboundingbox!(c::LarvaController, bb) = (c.boundingbox[] = bb)
getplayer(c::LarvaController) = c.player
getplayer(c) = getplayer(gethub(c))
getplayer(hub::ControllerHub) = haskey(hub, :player) ? hub[:player] : getplayer(hub[:larva])
gettags(c) = gettags(gethub(c)[:larva])
gettags(c::LarvaController) = c.tag_lut
# events
function hoverlarva!(controller::LarvaController, larva_id::LarvaID)
@debug "larva #$(larva_id) hovered"
larvaevents(controller).hovered[] = larva_id
end
function activatelarva!(controller::LarvaController, larva_id::Union{Nothing, LarvaID})
deactivatelarva!(controller)
larva = larvaevents(controller)
if isnothing(larva_id) || larva_id == NoLarva
larva.activated.val = NoLarva
else
larva.deactivated.val = NoLarva # optional
controller.activelarva[] = larva_id
@info "Larva #$(larva_id) activated"
larva.activated[] = larva_id
end
end
function deactivatelarva!(controller::LarvaController)
larva = larvaevents(controller)
current = larva.activated[]
current == NoLarva && return
@info "Larva #$(current) deactivated"
larva.activated.val = NoLarva # required
larva.deactivated[] = current
end
# to be implemented for each GUI "backend" (e.g. for WGL in wgl.jl)
function setedited! end
function addtosavequeue! end
setedited!(c, id) = setedited!(gethub(c)[:larvafilter], id)
addtosavequeue!(c, id) = addtosavequeue!(gethub(c)[:larvafilter], id)
function flag_active_larva_as_edited(controller; exclude=false)
id = getactivelarva(controller)[]
setedited!(controller, id)
if !exclude
addtosavequeue!(controller, id)
end
end
#
function computebbox(data; resolution=1)
bb = Meshes.boundingbox(data)
xy0 = coordinates(minimum(bb))
x0, y0 = @. floor(xy0 / resolution - 0.1) * resolution
xy1 = coordinates(maximum(bb))
x1, y1 = @. ceil(xy1 / resolution + 0.1) * resolution
return (x0, x1, y0, y1)
end
function setbounds!(view::Axis, ctrl::Union{LarvaController, ControllerHub}, data)
bbox = computebbox(data; resolution=10)
setboundingbox!(ctrl, bbox)
limits!(view, bbox...)
end
setbounds!(view::Axis, lb, ub) = limits!(view, lb[1], ub[1], lb[2], ub[2])
function slave(master::Observable, policy::ObservationPolicy=IndependentObservables())
newobservable(policy, master)
end
function slave(model::Dict{ID, LarvaModel},
policy::ObservationPolicy=IndependentObservables(),
) where {ID<:Integer}
model′ = Dict{ID, LarvaModel}()
for (id, larva) in pairs(model)
larva′ = LarvaModel(larva.id,
larva.alignedsteps,
larva.path,
larva.fullstates,
newobservable(policy, larva.usertags))
model′[id] = larva′
end
return model′
end
function slave(tag_lut::TagLUT,
policy::ObservationPolicy=IndependentObservables())
tag_lut′ = TagLUT()
for tag in tag_lut
push!(slave(tag, policy))
end
return newobservable(tag_lut′, policy)
end
function slave(tag::ObservableTag, policy::ObservationPolicy)
ObservableTag(tag.original,
newobservable(tag.name, policy),
newobservable(tag.color, policy),
newobservable(tag.active, policy))
end
function slave(master::LarvaController,
policy::ObservationPolicy=IndependentObservables(),
)
model = slave(master.model, policy)
tag_lut = slave(master.tag_lut, policy)
player = slave(master.player, policy)
LarvaController(master.hub,
model,
tag_lut,
master.activelarva,
player,
master.boundingbox)
end
stop!(controller::LarvaController) = stop!(controller.player)
# history
transactions(c) = get!(gethub(c), :transactions, Transaction[])
function setevents!(controller, tagvalidator::ValidatorBundle{ObservableTag})
tag = tagvalidator.model
on(tag.active) do b
if b
activatetag!(controller, tag)
else
deactivatetag!(controller, tag)
end
end
on(viewfailed(tagvalidator, :name)) do (_, failure)
tagevents(controller).renamefailed[] = (tag.name[] => failure)
end
on(tagvalidator, :name) do newname, curname
#renametag!(controller, tag)
transaction = curname => newname
push!(transactions(controller), RenameTag(transaction))
tagevents(controller).renamed[] = transaction
return Validate
end
onany(tag.active, tag.name, tag.color) do _, _, _
Observables.notify(gettags(controller))
end
end
# file metadata
struct MetadataItem
name::Validator{String}
value::Validator{String}
end
MetadataItem() = MetadataItem("", "")
MetadataItem(name::String, value::String) = MetadataItem(Validator(name), Validator(value))
MetadataItem(name::Symbol, args...) = MetadataItem(String(name), args...)
struct MetadataTable
entries::Vector{MetadataItem}
function MetadataTable(entries::Vector{MetadataItem};
connect::Bool=true)
table = new(entries)
if connect
for entry in entries
connect!(table, entry)
end
end
return table
end
end
MetadataTable() = MetadataTable(MetadataItem[])
MetadataTable(metadata::AbstractDict) = MetadataTable([MetadataItem(key, value) for (key, value) in pairs(metadata) if value isa String])
MetadataTable(run::Run) = MetadataTable(run.attributes[:metadata])
function asdict(metadata::MetadataTable)
dict = OrderedDict{String, String}()
for entry in metadata.entries
key, value = entry.name.model[], entry.value.model[]
if !isempty(key) && !isempty(value)
dict[key] = value
end
end
dict
end
Base.length(table::MetadataTable) = length(table.entries)
Base.haskey(table::MetadataTable, name::String) = any(item -> name == item.name.model[], table.entries)
function Base.push!(table::MetadataTable, item::MetadataItem)
push!(table.entries, item)
connect!(table, item)
return table
end
function connect!(table::MetadataTable, item::MetadataItem)
on(item.name.model) do val
if isempty(val)
item.value.model[] = ""
end
end
on(item.name, Val(1)) do newvalue
if !isempty(newvalue) && haskey(table, newvalue)
Invalidate
else
Validate
end
end
on(item.value, Val(1)) do newvalue
if !isempty(newvalue) && isempty(item.name.model[])
Invalidate
else
Validate
end
end
return table
end
function getmetadatatable(controller, nentries=nothing)
hub = gethub(controller)
table = nothing
try
table = hub[:metadatatable][]
catch
metadata = OrderedDict{Symbol, Any}()
if haskey(hub, :output)
dataset = hub[:output]
if haskey(dataset.attributes, :metadata)
merge!(metadata, dataset.attributes[:metadata])
end
if !isempty(dataset)
run = first(values(dataset))
if haskey(run.attributes, :metadata)
merge!(metadata, run.attributes[:metadata])
end
end
end
table = MetadataTable(metadata)
hub[:metadatatable] = Observable(table)
end
if !isnothing(nentries)
while length(table) < nentries
push!(table, MetadataItem())
end
end
return table
end
function Base.pairs(table::MetadataTable)
[(getview!(item.name), getview!(item.value)) for item in table.entries]
end
# session
function identifyclient(controller)
try
session = gethub(controller)[:session]
@logmsg Stub "Identifying client" session
catch
@warn "Cannot retrieve session and identify the client"
end
return "??"
end
# load animation
turn_load_animation_off(controller) = (gethub(controller)[:loadanimation][] = false)
turn_load_animation_on(controller) = (gethub(controller)[:loadanimation][] = true)