diff --git a/Manifest.toml b/Manifest.toml index cb947fc7d597428061bd6555a34cd6150c0c3161..be1ca3fafc7e2a39a580d9000435a12e4a9136bc 100644 --- a/Manifest.toml +++ b/Manifest.toml @@ -2,7 +2,7 @@ julia_version = "1.9.3" manifest_format = "2.0" -project_hash = "5a2d131ebba9918c9648a958a8d008d9dd7bc967" +project_hash = "bcd13ee640573e4651fc680662e595f5fd73216d" [[deps.AbstractFFTs]] deps = ["LinearAlgebra"] @@ -231,9 +231,9 @@ version = "0.5.0" [[deps.ChainRulesCore]] deps = ["Compat", "LinearAlgebra", "SparseArrays"] -git-tree-sha1 = "e30f2f4e20f7f186dc36529910beaedc60cfa644" +git-tree-sha1 = "b66b8f8e3db5d7835fb8cbe2589ffd1cd456e491" uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" -version = "1.16.0" +version = "1.17.0" [[deps.CircularArrays]] deps = ["OffsetArrays"] @@ -243,9 +243,9 @@ version = "1.3.2" [[deps.CodecZlib]] deps = ["TranscodingStreams", "Zlib_jll"] -git-tree-sha1 = "02aa26a4cf76381be7f66e020a3eddeb27b0a092" +git-tree-sha1 = "cd67fc487743b2f0fd4380d4cbd3a24660d0eec8" uuid = "944b1d66-785c-5afd-91f1-9de20f533193" -version = "0.7.2" +version = "0.7.3" [[deps.ColorBrewer]] deps = ["Colors", "JSON", "Test"] @@ -512,9 +512,9 @@ uuid = "2e619515-83b5-522b-bb60-26c02a35a201" version = "2.5.0+0" [[deps.Extents]] -git-tree-sha1 = "5e1e4c53fa39afe63a7d356e30452249365fba99" +git-tree-sha1 = "2140cd04483da90b2da7f99b2add0750504fc39c" uuid = "411431e0-e8b7-467b-b5e0-f676ba4f2910" -version = "0.1.1" +version = "0.1.2" [[deps.FFMPEG]] deps = ["FFMPEG_jll"] @@ -1363,9 +1363,9 @@ version = "10.42.0+0" [[deps.PDMats]] deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"] -git-tree-sha1 = "fcf8fd477bd7f33cb8dbb1243653fb0d415c256c" +git-tree-sha1 = "3d8af7d689bd228a3a6c2298a4e6d5f5944accb8" uuid = "90014a1f-27ba-587c-ab20-58faa44d9150" -version = "0.11.25" +version = "0.11.26" [[deps.PNGFiles]] deps = ["Base64", "CEnum", "ImageCore", "IndirectArrays", "OffsetArrays", "libpng_jll"] @@ -1411,7 +1411,7 @@ version = "2.7.2" [[deps.Persefone]] deps = ["Agents", "ArgParse", "CSV", "CairoMakie", "DataFrames", "DataFramesMeta", "Dates", "Distributed", "FileIO", "GeoArrays", "ImageMagick", "Logging", "LoggingExtras", "Pkg", "Random", "Serialization", "StableRNGs", "StatsBase", "TOML", "Test"] -git-tree-sha1 = "febeb873c49f62cc4c320f5461d0aef90823621d" +git-tree-sha1 = "b24521422c9b1a1099a8e9d1c4db06c4fdd7050a" repo-rev = "development" repo-url = "../model" uuid = "039acd1d-2a07-4b33-b082-83a1ff0fd136" @@ -1487,7 +1487,9 @@ version = "1.9.0" [[deps.QML]] deps = ["ColorTypes", "CxxWrap", "Libdl", "MacroTools", "Observables", "Qt6Wayland_jll", "Requires", "jlqml_jll"] -git-tree-sha1 = "8b4065aab80821daae07602de0293fa8f9a59222" +git-tree-sha1 = "676fd7e228e5895c3a45e3d5fea3a619133c3af8" +repo-rev = "main" +repo-url = "https://github.com/JuliaGraphics/QML.jl.git" uuid = "2db162a6-7e43-52c3-8d84-290c1c42d82a" version = "0.8.0" @@ -1603,6 +1605,12 @@ git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7" uuid = "ae029012-a4dd-5104-9daa-d747884805df" version = "1.3.0" +[[deps.ResumableFunctions]] +deps = ["MacroTools"] +git-tree-sha1 = "d0399c12a584c18ed77a351925cd07f5a9b32c36" +uuid = "c5292f4c-5179-55e1-98c5-05642aab7184" +version = "0.6.6" + [[deps.Rmath]] deps = ["Random", "Rmath_jll"] git-tree-sha1 = "f65dcb5fa46aee0cf9ed6274ccbd597adc49aa7b" @@ -1939,9 +1947,9 @@ uuid = "9d95f2ec-7b3d-5a63-8d20-e2491e220bb9" version = "1.4.3" [[deps.URIs]] -git-tree-sha1 = "b7a5e99f24892b6824a954199a45e9ffcc1c70f0" +git-tree-sha1 = "67db6cc7b3821e19ebe75791a9dd19c9b1188f2b" uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" -version = "1.5.0" +version = "1.5.1" [[deps.UUIDs]] deps = ["Random", "SHA"] @@ -2189,9 +2197,9 @@ version = "0.2.3+0" [[deps.jlqml_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Qt6Declarative_jll", "Qt6Svg_jll", "libcxxwrap_julia_jll"] -git-tree-sha1 = "a27c3b21a4f0fc3f659fb4efdc6147877cbe5d4c" +git-tree-sha1 = "d4a59a1816b53c6db845acb04ed8f74c0a3aa9de" uuid = "6b5019fb-a83d-5b4e-a9f7-678a36c28df7" -version = "0.5.3+0" +version = "0.5.4+0" [[deps.libaom_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] diff --git a/Project.toml b/Project.toml index 78a3fcd702b19285fda855c4664414ff351e4ede..bedbfecce453f485343a7eff4dbf31647572d048 100644 --- a/Project.toml +++ b/Project.toml @@ -15,4 +15,10 @@ Observables = "510215fc-4207-5dde-b226-833fc4488ee2" Persefone = "039acd1d-2a07-4b33-b082-83a1ff0fd136" QML = "2db162a6-7e43-52c3-8d84-290c1c42d82a" Qt6Base_jll = "c0090381-4147-56d7-9ebc-da0b1113ec56" +ResumableFunctions = "c5292f4c-5179-55e1-98c5-05642aab7184" TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +TableTransforms = "0d432bfd-3ee1-4ac1-886a-39f05cc69a3e" + +[compat] +GLMakie = "0.6" +Makie = "0.17" diff --git a/src/GUI.jl b/src/GUI.jl index e0c4c35e8c3f3a7beb70aca868a9b45286031cee..d0f4c6e790aa5940ea301ab89d6fcf671bc04b95 100644 --- a/src/GUI.jl +++ b/src/GUI.jl @@ -8,10 +8,15 @@ ## - https://github.com/barche/QmlJuliaExamples ## - https://doc.qt.io/qt-6/qtquick-index.html +#TODO define standard config file for Persefone Desktop + global model = nothing global landcovermap = nothing -#loadmodelobject(joinpath(dirname(@__FILE__), "../sampleinit.dat")) #initialise() +global runsimulation = nothing +global timer = nothing + const running = Observable(false) +const ticks = Observable(0) const date = Observable(today()) const progress = Observable(0.0) const delay = Observable(0.5) @@ -33,7 +38,12 @@ end function nextstep() global model - if date[] < model.date + if date[] >= @param(core.enddate) + running[] = false + runbuttontext[] = ">>" + runbuttontip[] = "Run" + return + elseif date[] < model.date # If we've "scrolled back" in time, step forward again without # having to resimulate (see `previousstep()`) date[] += Day(1) @@ -45,6 +55,7 @@ function nextstep() end progress[] = (date[]-@param(core.startdate)) / (@param(core.enddate)-@param(core.startdate)) + @emit updateMakie() println("Updated model: $(date[]-Day(1))") end @@ -53,58 +64,64 @@ function previousstep() # "scroll back" in time and visualise the state of the model at # a previous date global model + date[] <= @param(core.startdate) && return date[] -= Day(1) progress[] = (date[]-@param(core.startdate)) / (@param(core.enddate)-@param(core.startdate)) + @emit updateMakie() println("Backtracked model: $(date[]-Day(1))") end - -function runsimulation() - #XXX is this all I need to interrupt a run? + +@resumable function createrunfunction() while running[] nextstep() - sleep(delay) + #XXX to reduce interface lag, always only sleep for 0.1s? + sleep(delay[]) + @yield 1 end end function togglerunning() + global runsimulation, timer if running[] running[] = false + QML.stop(timer) runbuttontext[] = ">>" runbuttontip[] = "Run" else running[] = true + QML.start(timer) runbuttontext[] = "||" runbuttontip[] = "Pause" - runsimulation() + runsimulation = createrunfunction() end end function render_map(screen) global model, landcovermap + println("Updating map") figure = visualisemap(model, date[], landcovermap) display(screen, figure.scene) end function render_plot(screen) global model + println("Updating plot") figure = populationtrends(model) display(screen, figure.scene) end -@qmlfunction newsimulation -@qmlfunction nextstep -@qmlfunction previousstep -@qmlfunction togglerunning -@qmlfunction render_map -@qmlfunction render_plot - on(delay) do d - println("Delay is now $(round(d, digits=1)) seconds.") + println("Delay is now $(round(d, digits=1)) seconds.") #XXX DEBUG end on(running) do r - r ? println("Simulation started.") : println("Simulation stopped.") + ticks = 0 #prevent overflows + r ? println("Simulation started.") : println("Simulation stopped.") #XXX DEBUG +end + +on(ticks) do t + running[] && runsimulation() end function splashscreen() @@ -114,7 +131,7 @@ function splashscreen() QML.show(quick_view) #XXX exec_async() is currently still broken, but should be fixed soon # (https://github.com/JuliaGraphics/QML.jl/issues/174) - exec_async() + @async exec() end """ @@ -123,18 +140,30 @@ end The main function that creates the application. """ function launch() - global model - splashscreen() + global model, timer + #splashscreen() newsimulation() - # absolute path in case working dir is overridden + + @qmlfunction newsimulation + @qmlfunction nextstep + @qmlfunction previousstep + @qmlfunction togglerunning + + timer = QTimer() + qmlfile = joinpath(dirname(@__FILE__), "main.qml") loadqml(qmlfile, + timer = timer, vars = JuliaPropertyMap("running" => running, + "ticks" => ticks, "date" => date, "delay" => delay, "progress" => progress, "runbuttontext" => runbuttontext, - "runbuttontip" => runbuttontip)) + "runbuttontip" => runbuttontip), + render_map_callback = @safe_cfunction(render_map, Cvoid, (Any,)), + render_plot_callback = @safe_cfunction(render_plot, Cvoid, (Any,))) + @emit updateMakie() println("Launched Persefone Desktop.") exec() end diff --git a/src/PersefoneDesktop.jl b/src/PersefoneDesktop.jl index 3dc670bef8394bf92e54527182feb8990ae54d2c..4d9372911c2e14a3483db4cb85fd8566a41fa7d9 100644 --- a/src/PersefoneDesktop.jl +++ b/src/PersefoneDesktop.jl @@ -22,7 +22,8 @@ using CxxWrap, GLMakie, Persefone, - Observables + Observables, + ResumableFunctions # needed for Makie integration ENV["QSG_RENDER_LOOP"] = "basic" diff --git a/src/main.qml b/src/main.qml index fbaf1aff9c27eddb73ebb60437a23033bde46d7f..a95a4ee1c1284c394c0fe86876dc87fdef0160a6 100644 --- a/src/main.qml +++ b/src/main.qml @@ -3,17 +3,18 @@ /// This file defines the layout of the main window. /// +import org.julialang import QtQuick import QtQuick.Controls import QtQuick.Dialogs import QtQuick.Layouts -import org.julialang ApplicationWindow { id: mainWindow title: "Persefone.jl Desktop" width: 1024 height: 768 + visibility: "Maximized" visible: true menuBar: MenuBar { @@ -23,7 +24,10 @@ ApplicationWindow { text: "&New Simulation" onTriggered: { Julia.newsimulation() } //TODO select config file } - Action { text: "&Configure Simulation" } + Action { + text: "&Configure Simulation" + onTriggered: { SetupWindow.show() } //FIXME + } Action { text: "&Load Saved State" } Action { text: "&Save Current State" } Action { text: "Save Model &Output" } @@ -60,15 +64,16 @@ Distributed under the MIT license." } RowLayout { + spacing: 6 anchors.fill: parent // visualise the model map and the locations of animals - /* MakieViewport { */ - /* id: mapviewport */ - /* Layout.fillWidth: true */ - /* Layout.fillHeight: true */ - /* renderFunction: Julia.render_map() //TODO */ - /* } */ + MakieViewport { + id: mapviewport + Layout.fillWidth: true + Layout.fillHeight: true + renderFunction: render_map_callback + } ColumnLayout { @@ -104,12 +109,12 @@ Distributed under the MIT license." } // plot the population sizes over time - /* MakieViewport { */ - /* id: plotviewport */ - /* Layout.fillWidth: true */ - /* Layout.fillHeight: true */ - /* renderFunction: Julia.render_plot() //TODO */ - /* } */ + MakieViewport { + id: plotviewport + Layout.fillWidth: true + Layout.fillHeight: true + renderFunction: render_plot_callback //TODO + } } } @@ -148,8 +153,6 @@ Distributed under the MIT license." } ProgressBar { id: progressBar - from: 0 - to: 100 value: vars.progress Layout.fillWidth: true ToolTip.text: "Simulation progress" @@ -168,4 +171,19 @@ Distributed under the MIT license." } } } + + // set up connections and signals to update the simulation and the display + Connections { + target: timer + function onTimeout() { vars.ticks += 1 } + } + + JuliaSignals { + signal updateMakie() + onUpdateMakie: { + mapviewport.update(); + plotviewport.update(); + } + } + }