diff --git a/Project.toml b/Project.toml index 26871ce..e589dc5 100644 --- a/Project.toml +++ b/Project.toml @@ -2,7 +2,7 @@ name = "FunctionalStateMachine" uuid = "3e9e306e-7e3c-11e9-12d2-8f8f67a2f951" keywords = ["state machine"] desc = "Functional state machine with stepping and visualization tools." -version = "0.2.5" +version = "0.2.6" [deps] Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" diff --git a/src/FunctionalStateMachine.jl b/src/FunctionalStateMachine.jl index a559635..d3b76a5 100644 --- a/src/FunctionalStateMachine.jl +++ b/src/FunctionalStateMachine.jl @@ -9,6 +9,7 @@ export StateMachine, emptyState, exitStateMachine, + getIterCount, sandboxStateMachineStep, getStateLabel, histStateMachineTransitions diff --git a/src/StateMachine.jl b/src/StateMachine.jl index 55d21fd..7cb8c8b 100644 --- a/src/StateMachine.jl +++ b/src/StateMachine.jl @@ -40,16 +40,22 @@ usrdata = nothing while st(usrdata); end ``` """ -function (st::StateMachine{T})(userdata::T=nothing; - breakafter::Function=exitStateMachine, - verbose::Bool=false, - iterlimit::Int=-1, - recordhistory::Bool=false ) where {T} +function (st::StateMachine{T})( userdata::T=nothing; + breakafter::Function=exitStateMachine, + verbose::Bool=false, + iterlimit::Int=-1, + recordhistory::Bool=false, + housekeeping_cb::Function=(st)->() ) where {T} # st.iter += 1 + # verbose print to help debugging !verbose ? nothing : println("FSM $(st.name), iter=$(st.iter) -- $(st.next)") + # early exit plumbing retval = st.next != breakafter && (iterlimit == -1 || st.iter < iterlimit) + # record steps for later recordhistory ? push!(st.history, (Dates.now(), st.iter, deepcopy(st.next), deepcopy(userdata))) : nothing + # user has some special situation going on. + housekeeping_cb(st) st.next = st.next(userdata) return retval end @@ -73,6 +79,13 @@ function exitStateMachine(dummy) return emptyState end +""" + $SIGNATURES + +How many iterations has this `::StateMachine` stepped through. +""" +getIterCount(st::StateMachine) = st.iter + """ $SIGNATURES diff --git a/src/StateMachineAnimation.jl b/src/StateMachineAnimation.jl index 94e5d4a..1dba8bb 100644 --- a/src/StateMachineAnimation.jl +++ b/src/StateMachineAnimation.jl @@ -84,17 +84,17 @@ function histGraphStateMachineTransitions(stateVisits, allStates::Vector{Symbol} end -function renderStateMachineFrame(vg, - frame::Int; - title::String="", - viewerapp::String="eog", - fext::String="png", - engine::String="dot", - show::Bool=true, - folder::String="fsm_animation", - folderpath = "/tmp/$folder/", - timest::String="", - rmfirst::Bool=false ) +function renderStateMachineFrame( vg, + frame::Int; + title::String="", + viewerapp::String="eog", + fext::String="png", + engine::String="dot", + show::Bool=true, + folder::String="fsm_animation", + folderpath = "/tmp/$folder/", + timest::String="", + rmfirst::Bool=false ) # if rmfirst @warn "removing contents of $(folderpath)" @@ -113,7 +113,7 @@ function renderStateMachineFrame(vg, fid = open("$folderpath/dotscript.sh","w") str = "head -n `wc -l $dotfile | awk '{print \$1-1}'` $dotfile > $folderpath/tmpdot.dot" println(fid, str) - println(fid, "echo \"graph [label=\\\"$title, #$step, $(timest)\\\", labelloc=t];\" >> $folderpath/tmpdot.dot") + println(fid, "echo \"graph [label=\\\"$title, $(timest)\\\", labelloc=t];\" >> $folderpath/tmpdot.dot") println(fid, "echo \"}\" >> $folderpath/tmpdot.dot") close(fid) run(`chmod u+x $folderpath/dotscript.sh`) @@ -128,10 +128,10 @@ function renderStateMachineFrame(vg, return filepath end -function setVisGraphOnState!(vg, vertid; - xlabel::String="", - appendxlabel::String="", - vertColor::AbstractString="red" ) +function setVisGraphOnState!( vg, vertid; + xlabel::String="", + appendxlabel::String="", + vertColor::AbstractString="red" ) # vg.vertices[vertid].attributes["fillcolor"] = vertColor vg.vertices[vertid].attributes["style"] = "filled" @@ -154,18 +154,18 @@ function clearVisGraphAttributes!(vg) nothing end -function drawStateTransitionStep(hist, - step::Int, - vg, - lookup::Dict{Symbol,Int}; - title::String="", - viewerapp::String="eog", - fext::String="png", - engine::String="dot", - show::Bool=true, - folder::String="", - frame::Int=step, - vertColor::AbstractString="red" ) +function drawStateTransitionStep( hist, + step::Int, + vg, + lookup::Dict{Symbol,Int}; + title::String="", + viewerapp::String="eog", + fext::String="png", + engine::String="dot", + show::Bool=true, + folder::String="", + frame::Int=step, + vertColor::AbstractString="red" ) # lbl = getStateLabel(hist[step][3]) @@ -179,26 +179,26 @@ function drawStateTransitionStep(hist, # delete!(vert.attributes, "style") # identify and set the node - xlabel = length(title) > 0 ? (xlabelbefore != nothing ? xlabelbefore*"," : "")*title : "" + xlabel = length(title) > 0 ? (xlabelbefore !== nothing ? xlabelbefore*"," : "")*title : "" setVisGraphOnState!(vg, vertid, xlabel=xlabel, vertColor=vertColor ) # render state machine frame - filepath = renderStateMachineFrame(vg, - frame, - title=title, - viewerapp=viewerapp, - fext=fext, - engine=engine, - show=show, - folder=folder, - timest=string(split(string(hist[step][1]),'T')[end]), - rmfirst=false) + filepath = renderStateMachineFrame( vg, + frame, + title=title, + viewerapp=viewerapp, + fext=fext, + engine=engine, + show=show, + folder=folder, + timest=string(split(string(hist[step][1]),'T')[end]), + rmfirst=false) # # clean up the vg structure - fillcolorbefore == nothing ? delete!(vert.attributes, "fillcolor") : (vert.attributes["fillcolor"]=fillcolorbefore) - stylebefore == nothing ? delete!(vert.attributes, "style") : (vert.attributes["style"]=stylebefore) - xlabelbefore == nothing ? delete!(vert.attributes, "xlabel") : (vert.attributes["xlabel"]=xlabelbefore) + fillcolorbefore === nothing ? delete!(vert.attributes, "fillcolor") : (vert.attributes["fillcolor"]=fillcolorbefore) + stylebefore === nothing ? delete!(vert.attributes, "style") : (vert.attributes["style"]=stylebefore) + xlabelbefore === nothing ? delete!(vert.attributes, "xlabel") : (vert.attributes["xlabel"]=xlabelbefore) return filepath end @@ -398,8 +398,8 @@ end # @async run(`totem /tmp/caesar/csmCompound/out.ogv`) # draw_more_cb(::Tuple, ::Int, ::String) function animateStateMachineHistoryIntervalCompound(hists::Dict{Symbol, Vector{Tuple{DateTime, Int, <: Function, T}}}; - interval::Int=2, # frames - # frames::Int=100, + easyNames::Dict{Symbol,N}=Dict{Symbol,Nothing}(), + interval::Int=2, folderpath="/tmp/animatestate", title::String="", show::Bool=false, @@ -408,7 +408,7 @@ function animateStateMachineHistoryIntervalCompound(hists::Dict{Symbol, Vector{T draw_more_cb::Function=(x...)->(), fsmColors::Dict{Symbol,String}=Dict{Symbol,String}(), defaultColor::AbstractString="red", - autocolor_cb::Function=(histstep,csym,aniT)->(haskey(fsmColors, csym) ? fsmColors[csym] : defaultColor) ) where T + autocolor_cb::Function=(histstep,csym,aniT)->(haskey(fsmColors, csym) ? fsmColors[csym] : defaultColor) ) where {T, N} # # Dict{Symbol, Vector{Symbol}} stateVisits = Dict{Symbol, Vector{Symbol}}() @@ -433,6 +433,7 @@ function animateStateMachineHistoryIntervalCompound(hists::Dict{Symbol, Vector{T prevList = Dict{Symbol, Vector{Int}}() latestList = Dict{Symbol, Int}(whId => fsmStep) + prevT = aniT frameCount = 0 # loop across time @showprogress "exporting state machine images, $title " for stepCount in 1:totSteps @@ -451,18 +452,20 @@ function animateStateMachineHistoryIntervalCompound(hists::Dict{Symbol, Vector{T lbl = getStateLabel(hists[csym][lstep][3]) vertid = lookup[lbl] vertColor=autocolor_cb(hists[csym][lstep], csym, aniT) - # vertColor = haskey(fsmColors,csym) ? fsmColors[csym] : defaultColor - setVisGraphOnState!(vg, vertid, appendxlabel=string(csym)*",", vertColor=vertColor ) + easyn = haskey(easyNames, csym) ? easyNames[csym] : csym + setVisGraphOnState!(vg, vertid, appendxlabel="($easyn.$lstep),", vertColor=vertColor ) end # and draw as many frames for that setup for itr in 1:interval # increment frame counter frameCount += 1 + deltaT = (aniT - prevT).value + prevT = aniT # finally render one frame renderStateMachineFrame(vg, frameCount, - title=title, + title=title*" || dt=$deltaT ms ||", show=false, folderpath=folderpath, timest=string(split(string(aniT),' ')[1]),