Advance app runtime ownership and modernization docs
This commit is contained in:
16
AGENTS.md
16
AGENTS.md
@@ -30,11 +30,12 @@ Read these first:
|
|||||||
- Commit and push each verified successful progress slice.
|
- Commit and push each verified successful progress slice.
|
||||||
- Prefer larger coherent slices over tiny checkpoints, but keep docs/debt updated
|
- Prefer larger coherent slices over tiny checkpoints, but keep docs/debt updated
|
||||||
with each slice.
|
with each slice.
|
||||||
- After a verified slice is committed and pushed, reset conversation context
|
- Treat automatic compaction as a failure mode to avoid. Keep active context
|
||||||
before starting the next slice when practical, especially if the thread is
|
small, commit and push before the thread grows large, and reset conversation
|
||||||
approaching automatic compaction. Record all needed resume state in committed
|
context between verified slices when practical instead of carrying excess
|
||||||
code/docs first so the next thread can restart from `AGENTS.md`, roadmap/debt,
|
history forward. Record all needed resume state in committed code/docs first
|
||||||
and git history instead of relying on chat transcript context.
|
so the next thread can restart from `AGENTS.md`, roadmap/debt, and git
|
||||||
|
history instead of relying on chat transcript context.
|
||||||
- Do not revert user changes. Unrelated untracked notes, such as
|
- Do not revert user changes. Unrelated untracked notes, such as
|
||||||
`docs/human-review-notes.md`, should be left alone unless explicitly requested.
|
`docs/human-review-notes.md`, should be left alone unless explicitly requested.
|
||||||
- Use CMake as the source of truth. Legacy Visual Studio project files are not the
|
- Use CMake as the source of truth. Legacy Visual Studio project files are not the
|
||||||
@@ -42,8 +43,9 @@ Read these first:
|
|||||||
- Use `apply_patch` for manual source/doc edits.
|
- Use `apply_patch` for manual source/doc edits.
|
||||||
- For delegated work, follow `docs/modernization/director-workflow.md`: the
|
- For delegated work, follow `docs/modernization/director-workflow.md`: the
|
||||||
coordinator keeps integration locally, assigns direct worker tasks, uses
|
coordinator keeps integration locally, assigns direct worker tasks, uses
|
||||||
`gpt-5.3-codex-spark` workers by default, and gives them the exact project
|
`gpt-5.4-mini` workers by default, and gives them a minimal task packet with
|
||||||
context needed so they do not spend tokens re-reading repo docs.
|
only the build, test, and code-exploration context needed so they do not
|
||||||
|
spend tokens re-reading repo docs.
|
||||||
|
|
||||||
## Build And Test
|
## Build And Test
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ set(PP_PLATFORM_LINUX_SOURCES
|
|||||||
|
|
||||||
set(PP_PANOPAINTER_APP_SOURCES
|
set(PP_PANOPAINTER_APP_SOURCES
|
||||||
src/app.cpp
|
src/app.cpp
|
||||||
|
src/app_runtime.cpp
|
||||||
src/app_cloud.cpp
|
src/app_cloud.cpp
|
||||||
src/app_commands.cpp
|
src/app_commands.cpp
|
||||||
src/app_dialogs.cpp
|
src/app_dialogs.cpp
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# PanoPainter Capability Map
|
# PanoPainter Capability Map
|
||||||
|
|
||||||
Status: live
|
Status: live
|
||||||
Last updated: 2026-06-05
|
Last updated: 2026-06-16
|
||||||
|
|
||||||
This map is the preservation checklist for the modernization. When a component
|
This map is the preservation checklist for the modernization. When a component
|
||||||
is extracted, update the relevant rows with the owning component, test label,
|
is extracted, update the relevant rows with the owning component, test label,
|
||||||
@@ -59,6 +59,7 @@ and validation command.
|
|||||||
| Yoga layout | `Node` | `pp_ui_core` | Deterministic geometry fixtures |
|
| Yoga layout | `Node` | `pp_ui_core` | Deterministic geometry fixtures |
|
||||||
| Generic controls | `NodeButton`, sliders, text, images | `pp_ui_core` | Event dispatch, layout, ownership-handle, callback-disconnect, and destroy-during-callback tests |
|
| Generic controls | `NodeButton`, sliders, text, images | `pp_ui_core` | Event dispatch, layout, ownership-handle, callback-disconnect, and destroy-during-callback tests |
|
||||||
| PanoPainter panels/dialogs | `NodePanel*`, `NodeDialog*` | `pp_panopainter_ui`, `pp_ui_core` | UI automation scripts, command-dispatch view models, pure overlay lifetime tests, retained overlay-adapter build coverage, retained popup/dialog lifetime tests |
|
| PanoPainter panels/dialogs | `NodePanel*`, `NodeDialog*` | `pp_panopainter_ui`, `pp_ui_core` | UI automation scripts, command-dispatch view models, pure overlay lifetime tests, retained overlay-adapter build coverage, retained popup/dialog lifetime tests |
|
||||||
|
| UI ownership and thread affinity | `Node`, `LayoutManager`, `App` UI queue, retained callbacks | `pp_ui_core`, app runtime service, `pp_panopainter_ui` | Checked-handle dispatch, scoped callback disconnect, destroy-during-callback, close-during-dispatch, and UI-thread post/drain/shutdown coverage |
|
||||||
| Canvas viewport UI | `NodeCanvas` | `pp_panopainter_ui`, `pp_paint_renderer` | Input-to-command automation |
|
| Canvas viewport UI | `NodeCanvas` | `pp_panopainter_ui`, `pp_paint_renderer` | Input-to-command automation |
|
||||||
| Settings UI | `Settings`, `NodeSettings` | `pp_assets`, `pp_panopainter_ui` | Round-trip settings |
|
| Settings UI | `Settings`, `NodeSettings` | `pp_assets`, `pp_panopainter_ui` | Round-trip settings |
|
||||||
|
|
||||||
@@ -67,6 +68,7 @@ and validation command.
|
|||||||
| Capability | Current Area | Target Owner | Required Tests |
|
| Capability | Current Area | Target Owner | Required Tests |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| Mouse/keyboard/touch/gestures/cursor | `App`, platform entrypoints | `pp_app_core`, `pp_platform_api`, `pp_platform_*`, app | Cursor visibility decision tests, platform service dispatch tests, synthetic event playback |
|
| Mouse/keyboard/touch/gestures/cursor | `App`, platform entrypoints | `pp_app_core`, `pp_platform_api`, `pp_platform_*`, app | Cursor visibility decision tests, platform service dispatch tests, synthetic event playback |
|
||||||
|
| Render/UI task dispatch and worker shutdown | `App`, `Canvas`, retained worker threads, platform entrypoints | app runtime service, `pp_foundation`, `pp_platform_*` | Render/UI queue order, same-thread dispatch, cancellation, shutdown drain, and no-detached-worker ownership coverage |
|
||||||
| Wacom pressure | `WacomTablet` | `pp_platform_windows` | Adapter smoke with fallback |
|
| Wacom pressure | `WacomTablet` | `pp_platform_windows` | Adapter smoke with fallback |
|
||||||
| Clipboard/file picker/share/display | `App` platform methods | `pp_app_core`, `pp_platform_api`, `pp_platform_*` | Clipboard read/write, share saved-path, picked-path, and display-file decision tests, platform service display/share/picker dispatch tests, platform smoke or mocked service |
|
| Clipboard/file picker/share/display | `App` platform methods | `pp_app_core`, `pp_platform_api`, `pp_platform_*` | Clipboard read/write, share saved-path, picked-path, and display-file decision tests, platform service display/share/picker dispatch tests, platform smoke or mocked service |
|
||||||
| Virtual keyboard | `App`, platform entrypoints | `pp_app_core`, `pp_platform_api`, `pp_platform_*` | Keyboard visibility decision tests, platform service dispatch tests, platform smoke |
|
| Virtual keyboard | `App`, platform entrypoints | `pp_app_core`, `pp_platform_api`, `pp_platform_*` | Keyboard visibility decision tests, platform service dispatch tests, platform smoke |
|
||||||
|
|||||||
@@ -335,6 +335,10 @@ agent or engineer to remove them without reconstructing context from chat.
|
|||||||
routes the stroke preset/brush/dual-brush/pattern popup open-close flow through
|
routes the stroke preset/brush/dual-brush/pattern popup open-close flow through
|
||||||
checked overlay handles and explicit partial-open cleanup; migration is still
|
checked overlay handles and explicit partial-open cleanup; migration is still
|
||||||
pending in remaining panel families.
|
pending in remaining panel families.
|
||||||
|
- 2026-06-16: `DEBT-0063` was narrowed again. `src/node_panel_stroke.cpp` now
|
||||||
|
routes stroke popup close/reset through a shared overlay helper, and
|
||||||
|
`src/node_popup_menu.cpp` now keeps the popup alive through selection dispatch
|
||||||
|
with scoped ownership instead of raw `this` callbacks.
|
||||||
- 2026-06-14: `DEBT-0063` was narrowed again. `src/node_panel_brush.cpp` now
|
- 2026-06-14: `DEBT-0063` was narrowed again. `src/node_panel_brush.cpp` now
|
||||||
routes brush popup-menu open-close flow through checked overlay handles and
|
routes brush popup-menu open-close flow through checked overlay handles and
|
||||||
handle-based close on selection.
|
handle-based close on selection.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Modernization Coordinator Workflow
|
# Modernization Coordinator Workflow
|
||||||
|
|
||||||
Status: live
|
Status: live
|
||||||
Last updated: 2026-06-14
|
Last updated: 2026-06-16
|
||||||
|
|
||||||
Use this workflow when the user explicitly asks for subagents, delegation, a
|
Use this workflow when the user explicitly asks for subagents, delegation, a
|
||||||
coordinator, or parallel agent work. Do not spawn subagents just because a task
|
coordinator, or parallel agent work. Do not spawn subagents just because a task
|
||||||
@@ -16,10 +16,16 @@ layer.
|
|||||||
|
|
||||||
- Save main-thread tokens by keeping implementation and focused lookup out of
|
- Save main-thread tokens by keeping implementation and focused lookup out of
|
||||||
the coordinator context.
|
the coordinator context.
|
||||||
|
- Treat thread compaction as wasted budget. Prefer small active context,
|
||||||
|
committed resume state, and fresh follow-on threads over carrying long stale
|
||||||
|
history.
|
||||||
- Keep each implementation slice measurable, validated, committed, and pushed.
|
- Keep each implementation slice measurable, validated, committed, and pushed.
|
||||||
- Avoid merge conflicts by giving every worker a disjoint task and file scope.
|
- Avoid merge conflicts by giving every worker a disjoint task and file scope.
|
||||||
- Keep workers stateless between tasks: when a worker finishes, integrate or
|
- Do not leave workers idle. Either give a worker another coherent follow-on
|
||||||
reject the result, then clear that worker context before the next assignment.
|
task while its context is still useful, or close it promptly.
|
||||||
|
- Reuse worker context only for closely related follow-on tasks in the same
|
||||||
|
local area; otherwise close the worker and start a fresh one with a new
|
||||||
|
minimal packet.
|
||||||
- Keep prompts dense with the exact project/task context workers need so they do
|
- Keep prompts dense with the exact project/task context workers need so they do
|
||||||
not spend tokens re-reading repo docs or re-parsing broad areas.
|
not spend tokens re-reading repo docs or re-parsing broad areas.
|
||||||
- Keep communication terse: no fillers, no cheerleading, no narrative padding.
|
- Keep communication terse: no fillers, no cheerleading, no narrative padding.
|
||||||
@@ -40,7 +46,8 @@ The coordinator is the main agent in the user thread. The coordinator owns:
|
|||||||
- running final validation
|
- running final validation
|
||||||
- updating docs/debt/tasks
|
- updating docs/debt/tasks
|
||||||
- committing and pushing the verified slice
|
- committing and pushing the verified slice
|
||||||
- resetting worker context after each completed or rejected task
|
- either assigning coherent follow-on work to an active worker or closing that
|
||||||
|
worker once its useful context window is over
|
||||||
|
|
||||||
The coordinator should keep local work minimal. It may do a quick blocking
|
The coordinator should keep local work minimal. It may do a quick blocking
|
||||||
check before delegation, such as reading task rows, checking git status, or
|
check before delegation, such as reading task rows, checking git status, or
|
||||||
@@ -49,13 +56,16 @@ code or test work, delegate it directly to a worker whenever the scope can be
|
|||||||
made clear.
|
made clear.
|
||||||
|
|
||||||
The coordinator must front-load context. Workers should not be told to "read
|
The coordinator must front-load context. Workers should not be told to "read
|
||||||
the roadmap" or "inspect the repo" unless that is the task. The coordinator is
|
the roadmap", "read AGENTS.md", or "inspect the repo" unless that is the task.
|
||||||
responsible for extracting and passing:
|
The coordinator is responsible for extracting and passing:
|
||||||
|
|
||||||
- task ids and done checks
|
- task ids and done checks
|
||||||
- debt ids and removal conditions that matter
|
- debt ids and removal conditions that matter
|
||||||
- exact write scope and allowed read scope
|
- exact write scope and allowed read scope
|
||||||
- required validation commands
|
- required validation commands
|
||||||
|
- the specific build/test preset or target names the worker may need
|
||||||
|
- the exact code-exploration tools to use for the slice, such as `rg` or the
|
||||||
|
compiler-aware `clangd_nav.py` helper
|
||||||
- relevant file paths, code references, and current behavior notes
|
- relevant file paths, code references, and current behavior notes
|
||||||
- any repo rules or user constraints that materially affect the task
|
- any repo rules or user constraints that materially affect the task
|
||||||
|
|
||||||
@@ -68,6 +78,11 @@ Workers do not own repo discovery. They start from the coordinator-provided
|
|||||||
context packet and stay inside the assigned scope unless they hit a blocker that
|
context packet and stay inside the assigned scope unless they hit a blocker that
|
||||||
requires a narrow follow-up question.
|
requires a narrow follow-up question.
|
||||||
|
|
||||||
|
Workers may be kept alive for more than one assignment only when the next task
|
||||||
|
is coherent with the current one: same subsystem, overlapping read scope,
|
||||||
|
similar validation path, and no avoidable context rebuild. Do not keep workers
|
||||||
|
alive just because a slot is available.
|
||||||
|
|
||||||
Every worker and explorer must be told:
|
Every worker and explorer must be told:
|
||||||
|
|
||||||
- this repository may have other agents working in parallel
|
- this repository may have other agents working in parallel
|
||||||
@@ -80,13 +95,13 @@ Every worker and explorer must be told:
|
|||||||
|
|
||||||
| Work Type | Model | Reasoning Effort | Use |
|
| Work Type | Model | Reasoning Effort | Use |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| Coordinator orchestration and integration | inherited main-thread model or `gpt-5.4-mini` | `low` or `medium` | Scope selection, task routing, conflict checks, validation, docs/debt updates, commits, pushes. |
|
| Coordinator orchestration and integration | `gpt-5.4` | `low` or `medium` | Scope selection, task routing, conflict checks, validation, docs/debt updates, commits, pushes, and worker packet preparation. |
|
||||||
| Direct worker coding task | `gpt-5.3-codex-spark` | `medium` | Bounded implementation in known files with coordinator-supplied context. |
|
| Direct worker coding task | `gpt-5.4-mini` | `medium` | Bounded implementation in known files with coordinator-supplied context. |
|
||||||
| Direct worker lookup or inventory | `gpt-5.3-codex-spark` | `low` or `medium` | `rg` inventory, file ownership map, simple grep-based answers. |
|
| Direct worker lookup or inventory | `gpt-5.4-mini` | `low` or `medium` | `rg` inventory, file ownership map, simple grep-based answers. |
|
||||||
| Mechanical docs cleanup | `gpt-5.3-codex-spark` | `low` | Formatting, table updates, command normalization. |
|
| Mechanical docs cleanup | `gpt-5.4-mini` | `low` | Formatting, table updates, command normalization. |
|
||||||
| Coordinator-only escalation | inherited higher model only when explicitly justified | inherited | Resolve architecture ambiguity, conflict integration, or task decomposition failures without adding a captain layer. |
|
| Coordinator-only escalation | inherited higher model only when explicitly justified | inherited | Resolve architecture ambiguity, conflict integration, or task decomposition failures without adding a captain layer. |
|
||||||
|
|
||||||
Workers default to `gpt-5.3-codex-spark`. If a task looks too broad or risky
|
Workers default to `gpt-5.4-mini`. If a task looks too broad or risky
|
||||||
for that model, the coordinator should decompose it further or keep the narrow
|
for that model, the coordinator should decompose it further or keep the narrow
|
||||||
integration step locally instead of inserting an extra management tier.
|
integration step locally instead of inserting an extra management tier.
|
||||||
|
|
||||||
@@ -96,33 +111,46 @@ integration step locally instead of inserting an extra management tier.
|
|||||||
ids, validation commands, and only the necessary excerpts.
|
ids, validation commands, and only the necessary excerpts.
|
||||||
- Use `fork_context=true` only when prior conversation details are essential and
|
- Use `fork_context=true` only when prior conversation details are essential and
|
||||||
not already captured in the worker prompt.
|
not already captured in the worker prompt.
|
||||||
|
- Do not let the coordinator thread drift toward compaction. Once a verified
|
||||||
|
slice is committed and pushed, prefer a fresh thread for the next slice if
|
||||||
|
the remaining context is no longer tight.
|
||||||
- Do not paste large logs into prompts. Point workers at log paths and ask for
|
- Do not paste large logs into prompts. Point workers at log paths and ask for
|
||||||
the smallest relevant excerpt.
|
the smallest relevant excerpt.
|
||||||
- Do not ask workers to broadly read `AGENTS.md`, the roadmap, or the debt log.
|
- Do not ask workers to broadly read `AGENTS.md`, the roadmap, the debt log, or
|
||||||
Summarize the exact rules and rows they need.
|
other repo-wide docs. Summarize the exact rules and rows they need.
|
||||||
|
- Default worker context to a minimal operating packet: task id, assigned file
|
||||||
|
scope, build command, test command, and code-exploration command hints.
|
||||||
- Keep worker prompts compact but complete. Shorter is good only if it still
|
- Keep worker prompts compact but complete. Shorter is good only if it still
|
||||||
removes the need for worker-side repo rediscovery.
|
removes the need for worker-side repo rediscovery.
|
||||||
- Ask for compact final reports: changed files, result, validation, blockers,
|
- Ask for compact final reports: changed files, result, validation, blockers,
|
||||||
next recommendation.
|
next recommendation.
|
||||||
- Close completed workers after their results are integrated or rejected.
|
- Keep active workers busy with another coherent task when that is cheaper than
|
||||||
|
restarting context; otherwise close them immediately after integration.
|
||||||
|
- Close workers that are done, blocked, or no longer have a strong context
|
||||||
|
advantage so they do not accumulate and saturate worker slots.
|
||||||
- Prefer the smallest number of concurrent workers that keeps disjoint work
|
- Prefer the smallest number of concurrent workers that keeps disjoint work
|
||||||
moving.
|
moving.
|
||||||
- Use rolling integration: wait for whichever worker finishes first, process the
|
- Use rolling integration: wait for whichever worker finishes first, process the
|
||||||
result, then launch the next disjoint worker with a fresh context window.
|
result, then either reuse that worker for the next coherent slice or close it
|
||||||
|
before launching a fresh worker for unrelated work.
|
||||||
|
- Prefer committed repo state over chat history as the handoff mechanism between
|
||||||
|
slices so worker and coordinator prompts stay short.
|
||||||
|
|
||||||
## Delegation Flow
|
## Delegation Flow
|
||||||
|
|
||||||
1. Coordinator picks one or more `Ready` tasks from
|
1. Coordinator picks one or more `Ready` tasks from
|
||||||
`docs/modernization/tasks.md` with disjoint write scopes.
|
`docs/modernization/tasks.md` with disjoint write scopes.
|
||||||
2. Coordinator splits each task into direct worker-sized units.
|
2. Coordinator splits each task into direct worker-sized units, grouping
|
||||||
|
coherent follow-on work when one worker can finish it efficiently without
|
||||||
|
broadening scope.
|
||||||
3. Coordinator prepares a context packet for each worker with the exact task
|
3. Coordinator prepares a context packet for each worker with the exact task
|
||||||
requirements, file scope, validation commands, and relevant project details.
|
requirements, file scope, validation commands, and relevant project details.
|
||||||
4. Coordinator assigns the task directly to a `gpt-5.3-codex-spark` worker or
|
4. Coordinator assigns the task directly to a `gpt-5.4-mini` worker or
|
||||||
explorer.
|
explorer.
|
||||||
5. Worker returns changed files, validation, blockers, and any narrow
|
5. Worker returns changed files, validation, blockers, and any narrow
|
||||||
integration notes.
|
integration notes.
|
||||||
6. Coordinator reviews for scope conflicts, integrates the result, and clears
|
6. Coordinator reviews for scope conflicts, integrates the result, and decides
|
||||||
that worker context before assigning the next task.
|
whether to give that same worker another coherent task or close it.
|
||||||
7. Coordinator runs the listed validation command or the quiet checkpoint
|
7. Coordinator runs the listed validation command or the quiet checkpoint
|
||||||
wrapper for each integrated slice.
|
wrapper for each integrated slice.
|
||||||
8. Coordinator updates `tasks.md`, `debt.md`, and `roadmap.md` if task state or
|
8. Coordinator updates `tasks.md`, `debt.md`, and `roadmap.md` if task state or
|
||||||
@@ -132,7 +160,7 @@ integration step locally instead of inserting an extra management tier.
|
|||||||
## Coordinator Prompt Template For A Worker
|
## Coordinator Prompt Template For A Worker
|
||||||
|
|
||||||
```text
|
```text
|
||||||
You are a `gpt-5.3-codex-spark` worker on PanoPainter. Other agents may be
|
You are a `gpt-5.4-mini` worker on PanoPainter. Other agents may be
|
||||||
editing nearby files; do not revert unrelated changes.
|
editing nearby files; do not revert unrelated changes.
|
||||||
|
|
||||||
Task source: docs/modernization/tasks.md task(s) <TASK-ID-LIST>.
|
Task source: docs/modernization/tasks.md task(s) <TASK-ID-LIST>.
|
||||||
@@ -142,15 +170,24 @@ Debt ids: <DEBT-LIST>.
|
|||||||
Write scope: <FILES/DIRS ONLY>.
|
Write scope: <FILES/DIRS ONLY>.
|
||||||
Read scope: <FILES/DIRS>.
|
Read scope: <FILES/DIRS>.
|
||||||
Validation: <COMMANDS>.
|
Validation: <COMMANDS>.
|
||||||
|
Code exploration: <RG OR CLANGD_NAV COMMANDS TO USE>.
|
||||||
|
|
||||||
Repo constraints you must follow:
|
Repo constraints you must follow:
|
||||||
- <ONLY THE RELEVANT RULES>
|
- <ONLY THE RELEVANT RULES>
|
||||||
|
|
||||||
Current context you should rely on instead of broad repo/doc review:
|
Minimal context you should rely on instead of broad repo/doc review:
|
||||||
- <CURRENT BEHAVIOR NOTE>
|
- <CURRENT BEHAVIOR NOTE>
|
||||||
- <RELEVANT FILE OR SYMBOL NOTE>
|
- <RELEVANT FILE OR SYMBOL NOTE>
|
||||||
- <WHY THIS SLICE IS SAFE / WHAT MUST NOT CHANGE>
|
- <WHY THIS SLICE IS SAFE / WHAT MUST NOT CHANGE>
|
||||||
|
|
||||||
|
Do not read repo-wide docs unless this task explicitly requires it. Use only
|
||||||
|
the supplied context, the listed file scope, and the build/test/code-exploration
|
||||||
|
commands above.
|
||||||
|
|
||||||
|
If the coordinator gives you a second task, accept it only when it is coherent
|
||||||
|
with the current scope and does not require broad repo rediscovery. Otherwise
|
||||||
|
say that a fresh worker should be used.
|
||||||
|
|
||||||
Make the smallest behavior-preserving change that satisfies the done checks.
|
Make the smallest behavior-preserving change that satisfies the done checks.
|
||||||
Do not spend tokens on broad document review or inventory outside the assigned
|
Do not spend tokens on broad document review or inventory outside the assigned
|
||||||
scope unless the task explicitly requires it. If the task is larger than
|
scope unless the task explicitly requires it. If the task is larger than
|
||||||
@@ -187,6 +224,11 @@ Return only:
|
|||||||
- Focused validation for the task passed or the failure is documented.
|
- Focused validation for the task passed or the failure is documented.
|
||||||
- `docs/modernization/debt.md` changed when debt was narrowed or closed.
|
- `docs/modernization/debt.md` changed when debt was narrowed or closed.
|
||||||
- `docs/modernization/tasks.md` score changed only for `Done` tasks.
|
- `docs/modernization/tasks.md` score changed only for `Done` tasks.
|
||||||
- Each worker result was integrated before that worker context was reused.
|
- Each worker result was integrated before that worker was reused.
|
||||||
|
- Reused workers only handled coherent follow-on tasks with a real context
|
||||||
|
advantage.
|
||||||
|
- Done or blocked workers were closed instead of being left idle.
|
||||||
|
- The coordinator did not carry unnecessary stale history when a fresh thread
|
||||||
|
would have been cheaper than compaction.
|
||||||
- The commit contains one coherent slice.
|
- The commit contains one coherent slice.
|
||||||
- The branch was pushed.
|
- The branch was pushed.
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
5007
docs/modernization/tasks-done.md
Normal file
5007
docs/modernization/tasks-done.md
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
245
src/app.cpp
245
src/app.cpp
@@ -32,10 +32,6 @@
|
|||||||
|
|
||||||
App* App::I = nullptr; // singleton
|
App* App::I = nullptr; // singleton
|
||||||
|
|
||||||
std::deque<AppTask> App::render_tasklist;
|
|
||||||
std::mutex App::render_task_mutex;
|
|
||||||
std::condition_variable App::render_cv;
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
pp::app::CanvasToolMode canvas_tool_mode_from_canvas_mode(kCanvasMode mode) noexcept
|
pp::app::CanvasToolMode canvas_tool_mode_from_canvas_mode(kCanvasMode mode) noexcept
|
||||||
@@ -130,17 +126,6 @@ void apply_app_scissor_test(bool enabled)
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
std::thread App::render_thread;
|
|
||||||
std::thread::id App::render_thread_id;
|
|
||||||
bool App::render_running = false;
|
|
||||||
|
|
||||||
std::deque<AppTask> App::ui_tasklist;
|
|
||||||
std::mutex App::ui_task_mutex;
|
|
||||||
std::condition_variable App::ui_cv;
|
|
||||||
std::thread App::ui_thread;
|
|
||||||
std::thread::id App::ui_thread_id;
|
|
||||||
bool App::ui_running = false;
|
|
||||||
|
|
||||||
void App::create()
|
void App::create()
|
||||||
{
|
{
|
||||||
const auto initial_surface = pp::app::plan_app_initial_surface();
|
const auto initial_surface = pp::app::plan_app_initial_surface();
|
||||||
@@ -353,7 +338,7 @@ void App::async_redraw()
|
|||||||
if (plan.set_redraw)
|
if (plan.set_redraw)
|
||||||
redraw = true;
|
redraw = true;
|
||||||
if (plan.notify_ui)
|
if (plan.notify_ui)
|
||||||
ui_cv.notify_all();
|
runtime_.notify_ui_worker();
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::async_end()
|
void App::async_end()
|
||||||
@@ -699,252 +684,40 @@ void App::rec_loop()
|
|||||||
|
|
||||||
void App::render_thread_tick()
|
void App::render_thread_tick()
|
||||||
{
|
{
|
||||||
static uint32_t count = 0;
|
runtime_.render_thread_tick(*this);
|
||||||
render_thread_id = std::this_thread::get_id();
|
|
||||||
std::deque<AppTask> working_list;
|
|
||||||
pp::app::AppQueueDrainPlan drain_plan;
|
|
||||||
|
|
||||||
// move the task list locally to free the queue for other threads
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> lock(render_task_mutex);
|
|
||||||
drain_plan = pp::app::plan_app_render_queue_drain(render_tasklist.size());
|
|
||||||
render_running = drain_plan.mark_running;
|
|
||||||
if (!drain_plan.drain_tasks)
|
|
||||||
return;
|
|
||||||
working_list = std::move(render_tasklist);
|
|
||||||
}
|
|
||||||
|
|
||||||
// execute the tasks
|
|
||||||
if (drain_plan.wrap_in_render_context)
|
|
||||||
{
|
|
||||||
async_start();
|
|
||||||
while (!working_list.empty())
|
|
||||||
{
|
|
||||||
//LOG("render task %d", count);
|
|
||||||
//LOG("task %s", working_list.front().name.c_str());
|
|
||||||
count++;
|
|
||||||
working_list.front()();
|
|
||||||
working_list.pop_front();
|
|
||||||
}
|
|
||||||
async_end();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::render_thread_main()
|
void App::render_thread_main()
|
||||||
{
|
{
|
||||||
BT_SetTerminate();
|
runtime_.render_thread_main(*this);
|
||||||
|
|
||||||
uint32_t count = 0;
|
|
||||||
render_thread_id = std::this_thread::get_id();
|
|
||||||
render_running = pp::app::plan_app_thread_start().mark_running;
|
|
||||||
while (render_running)
|
|
||||||
{
|
|
||||||
std::deque<AppTask> working_list;
|
|
||||||
pp::app::AppQueueDrainPlan drain_plan;
|
|
||||||
|
|
||||||
// move the task list locally to free the queue for other threads
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> lock(render_task_mutex);
|
|
||||||
render_cv.wait(lock, [this] { return render_tasklist.empty() && render_running ? false : true; });
|
|
||||||
drain_plan = pp::app::plan_app_render_queue_drain(render_tasklist.size());
|
|
||||||
working_list = std::move(render_tasklist);
|
|
||||||
}
|
|
||||||
|
|
||||||
// execute the tasks
|
|
||||||
if (drain_plan.wrap_in_render_context)
|
|
||||||
{
|
|
||||||
async_start();
|
|
||||||
while (!working_list.empty())
|
|
||||||
{
|
|
||||||
//LOG("render task %d", count);
|
|
||||||
//LOG("task %s", working_list.front().name.c_str());
|
|
||||||
count++;
|
|
||||||
working_list.front()();
|
|
||||||
working_list.pop_front();
|
|
||||||
}
|
|
||||||
async_end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::ui_thread_tick()
|
void App::ui_thread_tick()
|
||||||
{
|
{
|
||||||
ui_thread_id = std::this_thread::get_id();
|
runtime_.ui_thread_tick(*this);
|
||||||
|
|
||||||
std::deque<AppTask> working_list;
|
|
||||||
pp::app::AppUiTickPlan tick_plan;
|
|
||||||
|
|
||||||
// move the task list locally to free the queue for other threads
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> lock(ui_task_mutex);
|
|
||||||
tick_plan = pp::app::plan_app_ui_thread_tick(ui_tasklist.size(), redraw);
|
|
||||||
ui_running = tick_plan.mark_running;
|
|
||||||
working_list = std::move(ui_tasklist);
|
|
||||||
}
|
|
||||||
|
|
||||||
// execute the tasks
|
|
||||||
if (tick_plan.execute_tasks)
|
|
||||||
{
|
|
||||||
while (!working_list.empty())
|
|
||||||
{
|
|
||||||
//LOG("ui task %d", count);
|
|
||||||
working_list.front()();
|
|
||||||
working_list.pop_front();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tick_plan.tick_app)
|
|
||||||
tick(0);
|
|
||||||
|
|
||||||
const auto redraw_plan = pp::app::plan_app_ui_loop_redraw(redraw, 0);
|
|
||||||
if (redraw_plan.enqueue_render_frame)
|
|
||||||
{
|
|
||||||
if (redraw_plan.update_before_render)
|
|
||||||
update(0);
|
|
||||||
render_task([this]
|
|
||||||
{
|
|
||||||
bind_default_render_target();
|
|
||||||
clear();
|
|
||||||
draw(0);
|
|
||||||
async_swap();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::ui_thread_main()
|
void App::ui_thread_main()
|
||||||
{
|
{
|
||||||
BT_SetTerminate();
|
runtime_.ui_thread_main(*this);
|
||||||
|
|
||||||
uint32_t count = 0;
|
|
||||||
ui_thread_id = std::this_thread::get_id();
|
|
||||||
ui_running = pp::app::plan_app_thread_start().mark_running;
|
|
||||||
|
|
||||||
attach_ui_thread();
|
|
||||||
|
|
||||||
LOG("ui thread init()");
|
|
||||||
init();
|
|
||||||
|
|
||||||
auto t_start = std::chrono::high_resolution_clock::now();
|
|
||||||
float t_frame = 0;
|
|
||||||
float t_fps_counter = 0;
|
|
||||||
float t_reloader = 0;
|
|
||||||
int rendered_frames = 0;
|
|
||||||
while (ui_running)
|
|
||||||
{
|
|
||||||
std::deque<AppTask> working_list;
|
|
||||||
|
|
||||||
// move the task list locally to free the queue for other threads
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> lock(ui_task_mutex);
|
|
||||||
ui_cv.wait_for(lock, std::chrono::milliseconds(idle_ms),
|
|
||||||
[this] { return ui_tasklist.empty() && ui_running ? false : true; });
|
|
||||||
working_list = std::move(ui_tasklist);
|
|
||||||
}
|
|
||||||
|
|
||||||
// execute the tasks
|
|
||||||
if (!working_list.empty())
|
|
||||||
{
|
|
||||||
while (!working_list.empty())
|
|
||||||
{
|
|
||||||
//LOG("ui task %d", count);
|
|
||||||
count++;
|
|
||||||
working_list.front()();
|
|
||||||
working_list.pop_front();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto t_now = std::chrono::high_resolution_clock::now();
|
|
||||||
float dt = std::chrono::duration<float>(t_now - t_start).count();
|
|
||||||
t_start = t_now;
|
|
||||||
|
|
||||||
const auto timer_plan = pp::app::plan_app_ui_loop_timers(
|
|
||||||
dt,
|
|
||||||
t_frame,
|
|
||||||
t_fps_counter,
|
|
||||||
t_reloader,
|
|
||||||
rendered_frames,
|
|
||||||
platform_enables_live_asset_reloading());
|
|
||||||
if (timer_plan) {
|
|
||||||
if (timer_plan.value().update_platform_frame)
|
|
||||||
update_platform_frame(dt);
|
|
||||||
t_frame = timer_plan.value().frame_accumulator;
|
|
||||||
t_fps_counter = timer_plan.value().fps_accumulator;
|
|
||||||
t_reloader = timer_plan.value().reload_accumulator;
|
|
||||||
rendered_frames = timer_plan.value().rendered_frames_after_report;
|
|
||||||
|
|
||||||
if (timer_plan.value().report_rendered_frames)
|
|
||||||
report_rendered_frames(timer_plan.value().reported_frame_count);
|
|
||||||
|
|
||||||
if (timer_plan.value().check_live_asset_reload) {
|
|
||||||
if (ShaderManager::reload())
|
|
||||||
{
|
|
||||||
stroke->update_controls();
|
|
||||||
redraw = true;
|
|
||||||
}
|
|
||||||
if (layout.reload())
|
|
||||||
redraw = true;
|
|
||||||
if (layout_designer.reload())
|
|
||||||
redraw = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto redraw_plan = pp::app::plan_app_ui_loop_redraw(redraw, rendered_frames);
|
|
||||||
if (redraw_plan.tick_app)
|
|
||||||
tick(dt);
|
|
||||||
|
|
||||||
if (redraw_plan.enqueue_render_frame)
|
|
||||||
{
|
|
||||||
if (redraw_plan.update_before_render)
|
|
||||||
update(t_frame);
|
|
||||||
render_task([this, t_frame]
|
|
||||||
{
|
|
||||||
bind_default_render_target();
|
|
||||||
clear();
|
|
||||||
draw(t_frame);
|
|
||||||
async_swap();
|
|
||||||
});
|
|
||||||
if (redraw_plan.reset_frame_accumulator)
|
|
||||||
t_frame = 0;
|
|
||||||
rendered_frames = redraw_plan.rendered_frames;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
detach_ui_thread();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::render_thread_start()
|
void App::render_thread_start()
|
||||||
{
|
{
|
||||||
const auto plan = pp::app::plan_app_thread_start();
|
runtime_.render_thread_start(*this);
|
||||||
if (plan.start_thread)
|
|
||||||
render_thread = std::thread(&App::render_thread_main, this);
|
|
||||||
render_running = plan.mark_running;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::render_thread_stop()
|
void App::render_thread_stop()
|
||||||
{
|
{
|
||||||
const auto plan = pp::app::plan_app_thread_stop(render_thread.joinable());
|
runtime_.render_thread_stop();
|
||||||
if (plan.mark_not_running)
|
|
||||||
render_running = false;
|
|
||||||
if (plan.notify_worker)
|
|
||||||
render_cv.notify_all();
|
|
||||||
if (plan.join_thread)
|
|
||||||
render_thread.join();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::ui_thread_start()
|
void App::ui_thread_start()
|
||||||
{
|
{
|
||||||
const auto plan = pp::app::plan_app_thread_start();
|
runtime_.ui_thread_start(*this);
|
||||||
if (plan.start_thread)
|
|
||||||
ui_thread = std::thread(&App::ui_thread_main, this);
|
|
||||||
ui_running = plan.mark_running;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::ui_thread_stop()
|
void App::ui_thread_stop()
|
||||||
{
|
{
|
||||||
const auto plan = pp::app::plan_app_thread_stop(ui_thread.joinable());
|
runtime_.ui_thread_stop();
|
||||||
if (plan.mark_not_running)
|
|
||||||
ui_running = false;
|
|
||||||
if (plan.notify_worker)
|
|
||||||
ui_cv.notify_all();
|
|
||||||
if (plan.join_thread)
|
|
||||||
ui_thread.join();
|
|
||||||
}
|
}
|
||||||
|
|||||||
169
src/app.h
169
src/app.h
@@ -25,6 +25,7 @@
|
|||||||
#include "layout.h"
|
#include "layout.h"
|
||||||
#include "app_core/document_session.h"
|
#include "app_core/document_session.h"
|
||||||
#include "app_core/app_thread.h"
|
#include "app_core/app_thread.h"
|
||||||
|
#include "app_runtime.h"
|
||||||
|
|
||||||
namespace pp::platform {
|
namespace pp::platform {
|
||||||
class PlatformServices;
|
class PlatformServices;
|
||||||
@@ -76,21 +77,6 @@ struct VRController
|
|||||||
virtual float get_trigger_value() const { return 1.f; }
|
virtual float get_trigger_value() const { return 1.f; }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AppTask : public std::packaged_task<void()>
|
|
||||||
{
|
|
||||||
size_t task_id;
|
|
||||||
#ifdef _DEBUG
|
|
||||||
std::string name;
|
|
||||||
#endif
|
|
||||||
template<typename F> AppTask(F f) : std::packaged_task<void()>(f)
|
|
||||||
{
|
|
||||||
task_id = typeid(f).hash_code();
|
|
||||||
#ifdef _DEBUG
|
|
||||||
name = typeid(f).name();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class App
|
class App
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -345,16 +331,13 @@ public:
|
|||||||
|
|
||||||
void cmd_convert(std::string pano_path, std::string out_path);
|
void cmd_convert(std::string pano_path, std::string out_path);
|
||||||
|
|
||||||
|
AppRuntime& runtime() noexcept { return runtime_; }
|
||||||
|
const AppRuntime& runtime() const noexcept { return runtime_; }
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
// RENDER THREAD
|
// RENDER THREAD
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
static std::deque<AppTask> render_tasklist;
|
|
||||||
static std::mutex render_task_mutex;
|
|
||||||
static std::condition_variable render_cv;
|
|
||||||
static std::thread render_thread;
|
|
||||||
static std::thread::id render_thread_id;
|
|
||||||
static bool render_running;
|
|
||||||
void render_thread_tick();
|
void render_thread_tick();
|
||||||
void render_thread_main();
|
void render_thread_main();
|
||||||
void render_thread_start();
|
void render_thread_start();
|
||||||
@@ -362,94 +345,30 @@ public:
|
|||||||
|
|
||||||
bool is_render_thread()
|
bool is_render_thread()
|
||||||
{
|
{
|
||||||
return std::this_thread::get_id() == render_thread_id;
|
return runtime_.is_render_thread();
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't capture a reference to this ptr as the object may be destroyed
|
|
||||||
// by the time the task is executed
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
std::future<void> render_task_async(T task, bool unique = false)
|
std::future<void> render_task_async(T task, bool unique = false)
|
||||||
{
|
{
|
||||||
AppTask pt(task);
|
return runtime_.render_task_async(std::move(task), unique);
|
||||||
auto f = pt.get_future();
|
|
||||||
const auto dispatch = pp::app::plan_app_task_dispatch(
|
|
||||||
is_render_thread(),
|
|
||||||
unique,
|
|
||||||
0U,
|
|
||||||
render_running,
|
|
||||||
false,
|
|
||||||
false);
|
|
||||||
if (dispatch.execute_immediately)
|
|
||||||
{
|
|
||||||
pt();
|
|
||||||
}
|
|
||||||
else if (dispatch.queue_task)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(render_task_mutex);
|
|
||||||
const auto queue_dispatch = pp::app::plan_app_task_dispatch(
|
|
||||||
false,
|
|
||||||
unique,
|
|
||||||
render_tasklist.size(),
|
|
||||||
render_running,
|
|
||||||
false,
|
|
||||||
false);
|
|
||||||
// remove any previously queued task from the same lambda
|
|
||||||
if (queue_dispatch.remove_matching_unique_task)
|
|
||||||
render_tasklist.erase(std::remove_if(render_tasklist.begin(), render_tasklist.end(),
|
|
||||||
[id = pt.task_id](AppTask const& t){ return t.task_id == id; }), render_tasklist.end());
|
|
||||||
render_tasklist.push_back(std::move(pt));
|
|
||||||
}
|
|
||||||
if (dispatch.notify_worker)
|
|
||||||
render_cv.notify_all();
|
|
||||||
}
|
|
||||||
return f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void render_task(T task)
|
void render_task(T task)
|
||||||
{
|
{
|
||||||
AppTask pt(task);
|
runtime_.render_task(std::move(task));
|
||||||
auto f = pt.get_future();
|
|
||||||
const auto dispatch = pp::app::plan_app_task_dispatch(
|
|
||||||
is_render_thread(),
|
|
||||||
false,
|
|
||||||
0U,
|
|
||||||
render_running,
|
|
||||||
true,
|
|
||||||
false);
|
|
||||||
if (dispatch.execute_immediately)
|
|
||||||
{
|
|
||||||
pt();
|
|
||||||
}
|
|
||||||
else if (dispatch.queue_task)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(render_task_mutex);
|
|
||||||
render_tasklist.push_back(std::move(pt));
|
|
||||||
}
|
|
||||||
if (dispatch.notify_worker)
|
|
||||||
render_cv.notify_all();
|
|
||||||
}
|
|
||||||
if (dispatch.wait_for_completion)
|
|
||||||
f.get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void render_sync()
|
void render_sync()
|
||||||
{
|
{
|
||||||
render_task([] {});
|
runtime_.render_sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
// UI THREAD
|
// UI THREAD
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
static std::deque<AppTask> ui_tasklist;
|
|
||||||
static std::mutex ui_task_mutex;
|
|
||||||
static std::condition_variable ui_cv;
|
|
||||||
static std::thread ui_thread;
|
|
||||||
static std::thread::id ui_thread_id;
|
|
||||||
static bool ui_running;
|
|
||||||
void ui_thread_tick();
|
void ui_thread_tick();
|
||||||
void ui_thread_main();
|
void ui_thread_main();
|
||||||
void ui_thread_start();
|
void ui_thread_start();
|
||||||
@@ -457,83 +376,29 @@ public:
|
|||||||
|
|
||||||
bool is_ui_thread()
|
bool is_ui_thread()
|
||||||
{
|
{
|
||||||
return std::this_thread::get_id() == ui_thread_id;
|
return runtime_.is_ui_thread();
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't capture a reference to this ptr as the object may be destroyed
|
|
||||||
// by the time the task is executed
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
std::future<void> ui_task_async(T task, bool unique = false)
|
std::future<void> ui_task_async(T task, bool unique = false)
|
||||||
{
|
{
|
||||||
AppTask pt(task);
|
return runtime_.ui_task_async(std::move(task), unique);
|
||||||
auto f = pt.get_future();
|
|
||||||
const auto dispatch = pp::app::plan_app_task_dispatch(
|
|
||||||
is_ui_thread(),
|
|
||||||
unique,
|
|
||||||
0U,
|
|
||||||
ui_running,
|
|
||||||
false,
|
|
||||||
false);
|
|
||||||
if (dispatch.execute_immediately)
|
|
||||||
{
|
|
||||||
pt();
|
|
||||||
}
|
|
||||||
else if (dispatch.queue_task)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(ui_task_mutex);
|
|
||||||
const auto queue_dispatch = pp::app::plan_app_task_dispatch(
|
|
||||||
false,
|
|
||||||
unique,
|
|
||||||
ui_tasklist.size(),
|
|
||||||
ui_running,
|
|
||||||
false,
|
|
||||||
false);
|
|
||||||
// remove any previously queued task from the same lambda
|
|
||||||
if (queue_dispatch.remove_matching_unique_task)
|
|
||||||
ui_tasklist.erase(std::remove_if(ui_tasklist.begin(), ui_tasklist.end(),
|
|
||||||
[id = pt.task_id](AppTask const& t){ return t.task_id == id; }), ui_tasklist.end());
|
|
||||||
ui_tasklist.push_back(std::move(pt));
|
|
||||||
}
|
|
||||||
if (dispatch.notify_worker)
|
|
||||||
ui_cv.notify_all();
|
|
||||||
}
|
|
||||||
return f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void ui_task(T task)
|
void ui_task(T task)
|
||||||
{
|
{
|
||||||
AppTask pt(task);
|
runtime_.ui_task(std::move(task));
|
||||||
auto f = pt.get_future();
|
if (runtime_.request_redraw())
|
||||||
const auto dispatch = pp::app::plan_app_task_dispatch(
|
|
||||||
is_ui_thread(),
|
|
||||||
false,
|
|
||||||
0U,
|
|
||||||
ui_running,
|
|
||||||
true,
|
|
||||||
true);
|
|
||||||
if (dispatch.execute_immediately)
|
|
||||||
{
|
|
||||||
pt();
|
|
||||||
}
|
|
||||||
else if (dispatch.queue_task)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(ui_task_mutex);
|
|
||||||
ui_tasklist.push_back(std::move(pt));
|
|
||||||
}
|
|
||||||
if (dispatch.notify_worker)
|
|
||||||
ui_cv.notify_all();
|
|
||||||
}
|
|
||||||
if (dispatch.wait_for_completion)
|
|
||||||
f.get();
|
|
||||||
if (dispatch.request_redraw)
|
|
||||||
redraw = true;
|
redraw = true;
|
||||||
|
runtime_.clear_request_redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ui_sync()
|
void ui_sync()
|
||||||
{
|
{
|
||||||
ui_task([] {});
|
runtime_.ui_sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
AppRuntime runtime_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ void App::cloud_upload()
|
|||||||
|
|
||||||
void App::cloud_upload_all()
|
void App::cloud_upload_all()
|
||||||
{
|
{
|
||||||
std::thread([this] {
|
pp::panopainter::queue_legacy_cloud_worker_task([this] {
|
||||||
BT_SetTerminate();
|
BT_SetTerminate();
|
||||||
|
|
||||||
auto names = Asset::list_files(data_path, ".*\\.ppi");
|
auto names = Asset::list_files(data_path, ".*\\.ppi");
|
||||||
@@ -28,7 +28,7 @@ void App::cloud_upload_all()
|
|||||||
const auto status = pp::panopainter::execute_legacy_cloud_bulk_upload_plan(*this, plan);
|
const auto status = pp::panopainter::execute_legacy_cloud_bulk_upload_plan(*this, plan);
|
||||||
if (!status.ok())
|
if (!status.ok())
|
||||||
LOG("Cloud bulk upload action failed: %s", status.message);
|
LOG("Cloud bulk upload action failed: %s", status.message);
|
||||||
}).detach();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::cloud_browse()
|
void App::cloud_browse()
|
||||||
|
|||||||
258
src/app_runtime.cpp
Normal file
258
src/app_runtime.cpp
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "app_runtime.h"
|
||||||
|
|
||||||
|
#include "app.h"
|
||||||
|
|
||||||
|
bool AppRuntime::is_render_thread() const noexcept
|
||||||
|
{
|
||||||
|
return std::this_thread::get_id() == render_thread_id_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AppRuntime::is_ui_thread() const noexcept
|
||||||
|
{
|
||||||
|
return std::this_thread::get_id() == ui_thread_id_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppRuntime::notify_render_worker() noexcept
|
||||||
|
{
|
||||||
|
render_cv_.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppRuntime::notify_ui_worker() noexcept
|
||||||
|
{
|
||||||
|
ui_cv_.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppRuntime::render_thread_tick(App& app)
|
||||||
|
{
|
||||||
|
static uint32_t count = 0;
|
||||||
|
render_thread_id_ = std::this_thread::get_id();
|
||||||
|
std::deque<AppTask> working_list;
|
||||||
|
pp::app::AppQueueDrainPlan drain_plan;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(render_task_mutex_);
|
||||||
|
drain_plan = pp::app::plan_app_render_queue_drain(render_tasklist_.size());
|
||||||
|
render_running_ = drain_plan.mark_running;
|
||||||
|
if (!drain_plan.drain_tasks)
|
||||||
|
return;
|
||||||
|
working_list = std::move(render_tasklist_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drain_plan.wrap_in_render_context)
|
||||||
|
{
|
||||||
|
app.async_start();
|
||||||
|
while (!working_list.empty())
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
working_list.front()();
|
||||||
|
working_list.pop_front();
|
||||||
|
}
|
||||||
|
app.async_end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppRuntime::render_thread_main(App& app)
|
||||||
|
{
|
||||||
|
BT_SetTerminate();
|
||||||
|
|
||||||
|
render_thread_id_ = std::this_thread::get_id();
|
||||||
|
render_running_ = pp::app::plan_app_thread_start().mark_running;
|
||||||
|
while (render_running_)
|
||||||
|
{
|
||||||
|
std::deque<AppTask> working_list;
|
||||||
|
pp::app::AppQueueDrainPlan drain_plan;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(render_task_mutex_);
|
||||||
|
render_cv_.wait(lock, [this] { return render_tasklist_.empty() && render_running_ ? false : true; });
|
||||||
|
drain_plan = pp::app::plan_app_render_queue_drain(render_tasklist_.size());
|
||||||
|
working_list = std::move(render_tasklist_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drain_plan.wrap_in_render_context)
|
||||||
|
{
|
||||||
|
app.async_start();
|
||||||
|
while (!working_list.empty())
|
||||||
|
{
|
||||||
|
working_list.front()();
|
||||||
|
working_list.pop_front();
|
||||||
|
}
|
||||||
|
app.async_end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppRuntime::ui_thread_tick(App& app)
|
||||||
|
{
|
||||||
|
ui_thread_id_ = std::this_thread::get_id();
|
||||||
|
|
||||||
|
std::deque<AppTask> working_list;
|
||||||
|
pp::app::AppUiTickPlan tick_plan;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(ui_task_mutex_);
|
||||||
|
tick_plan = pp::app::plan_app_ui_thread_tick(ui_tasklist_.size(), app.redraw);
|
||||||
|
ui_running_ = tick_plan.mark_running;
|
||||||
|
working_list = std::move(ui_tasklist_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tick_plan.execute_tasks)
|
||||||
|
{
|
||||||
|
while (!working_list.empty())
|
||||||
|
{
|
||||||
|
working_list.front()();
|
||||||
|
working_list.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tick_plan.tick_app)
|
||||||
|
app.tick(0);
|
||||||
|
|
||||||
|
const auto redraw_plan = pp::app::plan_app_ui_loop_redraw(app.redraw, 0);
|
||||||
|
if (redraw_plan.enqueue_render_frame)
|
||||||
|
{
|
||||||
|
if (redraw_plan.update_before_render)
|
||||||
|
app.update(0);
|
||||||
|
app.render_task([&app]
|
||||||
|
{
|
||||||
|
app.bind_default_render_target();
|
||||||
|
app.clear();
|
||||||
|
app.draw(0);
|
||||||
|
app.async_swap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppRuntime::ui_thread_main(App& app)
|
||||||
|
{
|
||||||
|
BT_SetTerminate();
|
||||||
|
|
||||||
|
ui_thread_id_ = std::this_thread::get_id();
|
||||||
|
ui_running_ = pp::app::plan_app_thread_start().mark_running;
|
||||||
|
|
||||||
|
app.attach_ui_thread();
|
||||||
|
|
||||||
|
LOG("ui thread init()");
|
||||||
|
app.init();
|
||||||
|
|
||||||
|
auto t_start = std::chrono::high_resolution_clock::now();
|
||||||
|
float t_frame = 0;
|
||||||
|
float t_fps_counter = 0;
|
||||||
|
float t_reloader = 0;
|
||||||
|
int rendered_frames = 0;
|
||||||
|
while (ui_running_)
|
||||||
|
{
|
||||||
|
std::deque<AppTask> working_list;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(ui_task_mutex_);
|
||||||
|
ui_cv_.wait_for(lock, std::chrono::milliseconds(app.idle_ms),
|
||||||
|
[this] { return ui_tasklist_.empty() && ui_running_ ? false : true; });
|
||||||
|
working_list = std::move(ui_tasklist_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!working_list.empty())
|
||||||
|
{
|
||||||
|
while (!working_list.empty())
|
||||||
|
{
|
||||||
|
working_list.front()();
|
||||||
|
working_list.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto t_now = std::chrono::high_resolution_clock::now();
|
||||||
|
float dt = std::chrono::duration<float>(t_now - t_start).count();
|
||||||
|
t_start = t_now;
|
||||||
|
|
||||||
|
const auto timer_plan = pp::app::plan_app_ui_loop_timers(
|
||||||
|
dt,
|
||||||
|
t_frame,
|
||||||
|
t_fps_counter,
|
||||||
|
t_reloader,
|
||||||
|
rendered_frames,
|
||||||
|
app.platform_enables_live_asset_reloading());
|
||||||
|
if (timer_plan) {
|
||||||
|
if (timer_plan.value().update_platform_frame)
|
||||||
|
app.update_platform_frame(dt);
|
||||||
|
t_frame = timer_plan.value().frame_accumulator;
|
||||||
|
t_fps_counter = timer_plan.value().fps_accumulator;
|
||||||
|
t_reloader = timer_plan.value().reload_accumulator;
|
||||||
|
rendered_frames = timer_plan.value().rendered_frames_after_report;
|
||||||
|
|
||||||
|
if (timer_plan.value().report_rendered_frames)
|
||||||
|
app.report_rendered_frames(timer_plan.value().reported_frame_count);
|
||||||
|
|
||||||
|
if (timer_plan.value().check_live_asset_reload) {
|
||||||
|
if (ShaderManager::reload())
|
||||||
|
{
|
||||||
|
app.stroke->update_controls();
|
||||||
|
app.redraw = true;
|
||||||
|
}
|
||||||
|
if (app.layout.reload())
|
||||||
|
app.redraw = true;
|
||||||
|
if (app.layout_designer.reload())
|
||||||
|
app.redraw = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto redraw_plan = pp::app::plan_app_ui_loop_redraw(app.redraw, rendered_frames);
|
||||||
|
if (redraw_plan.tick_app)
|
||||||
|
app.tick(dt);
|
||||||
|
|
||||||
|
if (redraw_plan.enqueue_render_frame)
|
||||||
|
{
|
||||||
|
if (redraw_plan.update_before_render)
|
||||||
|
app.update(t_frame);
|
||||||
|
app.render_task([&app, t_frame]
|
||||||
|
{
|
||||||
|
app.bind_default_render_target();
|
||||||
|
app.clear();
|
||||||
|
app.draw(t_frame);
|
||||||
|
app.async_swap();
|
||||||
|
});
|
||||||
|
if (redraw_plan.reset_frame_accumulator)
|
||||||
|
t_frame = 0;
|
||||||
|
rendered_frames = redraw_plan.rendered_frames;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
app.detach_ui_thread();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppRuntime::render_thread_start(App& app)
|
||||||
|
{
|
||||||
|
const auto plan = pp::app::plan_app_thread_start();
|
||||||
|
if (plan.start_thread)
|
||||||
|
render_thread_ = std::thread(&AppRuntime::render_thread_main, this, std::ref(app));
|
||||||
|
render_running_ = plan.mark_running;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppRuntime::render_thread_stop()
|
||||||
|
{
|
||||||
|
const auto plan = pp::app::plan_app_thread_stop(render_thread_.joinable());
|
||||||
|
if (plan.mark_not_running)
|
||||||
|
render_running_ = false;
|
||||||
|
if (plan.notify_worker)
|
||||||
|
render_cv_.notify_all();
|
||||||
|
if (plan.join_thread)
|
||||||
|
render_thread_.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppRuntime::ui_thread_start(App& app)
|
||||||
|
{
|
||||||
|
const auto plan = pp::app::plan_app_thread_start();
|
||||||
|
if (plan.start_thread)
|
||||||
|
ui_thread_ = std::thread(&AppRuntime::ui_thread_main, this, std::ref(app));
|
||||||
|
ui_running_ = plan.mark_running;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppRuntime::ui_thread_stop()
|
||||||
|
{
|
||||||
|
const auto plan = pp::app::plan_app_thread_stop(ui_thread_.joinable());
|
||||||
|
if (plan.mark_not_running)
|
||||||
|
ui_running_ = false;
|
||||||
|
if (plan.notify_worker)
|
||||||
|
ui_cv_.notify_all();
|
||||||
|
if (plan.join_thread)
|
||||||
|
ui_thread_.join();
|
||||||
|
}
|
||||||
216
src/app_runtime.h
Normal file
216
src/app_runtime.h
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "app_core/app_thread.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <deque>
|
||||||
|
#include <functional>
|
||||||
|
#include <future>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <typeinfo>
|
||||||
|
#include <utility>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
class App;
|
||||||
|
|
||||||
|
struct AppTask : public std::packaged_task<void()>
|
||||||
|
{
|
||||||
|
size_t task_id;
|
||||||
|
#ifdef _DEBUG
|
||||||
|
std::string name;
|
||||||
|
#endif
|
||||||
|
template<typename F> AppTask(F f) : std::packaged_task<void()>(f)
|
||||||
|
{
|
||||||
|
task_id = typeid(f).hash_code();
|
||||||
|
#ifdef _DEBUG
|
||||||
|
name = typeid(f).name();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class AppRuntime
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
[[nodiscard]] bool is_render_thread() const noexcept;
|
||||||
|
[[nodiscard]] bool is_ui_thread() const noexcept;
|
||||||
|
[[nodiscard]] bool request_redraw() const noexcept { return request_redraw_; }
|
||||||
|
void clear_request_redraw() noexcept { request_redraw_ = false; }
|
||||||
|
|
||||||
|
void notify_render_worker() noexcept;
|
||||||
|
void notify_ui_worker() noexcept;
|
||||||
|
|
||||||
|
void render_thread_tick(App& app);
|
||||||
|
void render_thread_main(App& app);
|
||||||
|
void render_thread_start(App& app);
|
||||||
|
void render_thread_stop();
|
||||||
|
|
||||||
|
void ui_thread_tick(App& app);
|
||||||
|
void ui_thread_main(App& app);
|
||||||
|
void ui_thread_start(App& app);
|
||||||
|
void ui_thread_stop();
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
std::future<void> render_task_async(T task, bool unique = false)
|
||||||
|
{
|
||||||
|
AppTask pt(task);
|
||||||
|
auto f = pt.get_future();
|
||||||
|
const auto dispatch = pp::app::plan_app_task_dispatch(
|
||||||
|
is_render_thread(),
|
||||||
|
unique,
|
||||||
|
0U,
|
||||||
|
render_running_,
|
||||||
|
false,
|
||||||
|
false);
|
||||||
|
if (dispatch.execute_immediately)
|
||||||
|
{
|
||||||
|
pt();
|
||||||
|
}
|
||||||
|
else if (dispatch.queue_task)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(render_task_mutex_);
|
||||||
|
const auto queue_dispatch = pp::app::plan_app_task_dispatch(
|
||||||
|
false,
|
||||||
|
unique,
|
||||||
|
render_tasklist_.size(),
|
||||||
|
render_running_,
|
||||||
|
false,
|
||||||
|
false);
|
||||||
|
if (queue_dispatch.remove_matching_unique_task)
|
||||||
|
render_tasklist_.erase(std::remove_if(render_tasklist_.begin(), render_tasklist_.end(),
|
||||||
|
[id = pt.task_id](AppTask const& t){ return t.task_id == id; }), render_tasklist_.end());
|
||||||
|
render_tasklist_.push_back(std::move(pt));
|
||||||
|
}
|
||||||
|
if (dispatch.notify_worker)
|
||||||
|
render_cv_.notify_all();
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void render_task(T task)
|
||||||
|
{
|
||||||
|
AppTask pt(task);
|
||||||
|
auto f = pt.get_future();
|
||||||
|
const auto dispatch = pp::app::plan_app_task_dispatch(
|
||||||
|
is_render_thread(),
|
||||||
|
false,
|
||||||
|
0U,
|
||||||
|
render_running_,
|
||||||
|
true,
|
||||||
|
false);
|
||||||
|
if (dispatch.execute_immediately)
|
||||||
|
{
|
||||||
|
pt();
|
||||||
|
}
|
||||||
|
else if (dispatch.queue_task)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(render_task_mutex_);
|
||||||
|
render_tasklist_.push_back(std::move(pt));
|
||||||
|
}
|
||||||
|
if (dispatch.notify_worker)
|
||||||
|
render_cv_.notify_all();
|
||||||
|
}
|
||||||
|
if (dispatch.wait_for_completion)
|
||||||
|
f.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_sync()
|
||||||
|
{
|
||||||
|
render_task([] {});
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
std::future<void> ui_task_async(T task, bool unique = false)
|
||||||
|
{
|
||||||
|
AppTask pt(task);
|
||||||
|
auto f = pt.get_future();
|
||||||
|
const auto dispatch = pp::app::plan_app_task_dispatch(
|
||||||
|
is_ui_thread(),
|
||||||
|
unique,
|
||||||
|
0U,
|
||||||
|
ui_running_,
|
||||||
|
false,
|
||||||
|
false);
|
||||||
|
if (dispatch.execute_immediately)
|
||||||
|
{
|
||||||
|
pt();
|
||||||
|
}
|
||||||
|
else if (dispatch.queue_task)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(ui_task_mutex_);
|
||||||
|
const auto queue_dispatch = pp::app::plan_app_task_dispatch(
|
||||||
|
false,
|
||||||
|
unique,
|
||||||
|
ui_tasklist_.size(),
|
||||||
|
ui_running_,
|
||||||
|
false,
|
||||||
|
false);
|
||||||
|
if (queue_dispatch.remove_matching_unique_task)
|
||||||
|
ui_tasklist_.erase(std::remove_if(ui_tasklist_.begin(), ui_tasklist_.end(),
|
||||||
|
[id = pt.task_id](AppTask const& t){ return t.task_id == id; }), ui_tasklist_.end());
|
||||||
|
ui_tasklist_.push_back(std::move(pt));
|
||||||
|
}
|
||||||
|
if (dispatch.notify_worker)
|
||||||
|
ui_cv_.notify_all();
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void ui_task(T task)
|
||||||
|
{
|
||||||
|
AppTask pt(task);
|
||||||
|
auto f = pt.get_future();
|
||||||
|
const auto dispatch = pp::app::plan_app_task_dispatch(
|
||||||
|
is_ui_thread(),
|
||||||
|
false,
|
||||||
|
0U,
|
||||||
|
ui_running_,
|
||||||
|
true,
|
||||||
|
true);
|
||||||
|
if (dispatch.execute_immediately)
|
||||||
|
{
|
||||||
|
pt();
|
||||||
|
}
|
||||||
|
else if (dispatch.queue_task)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(ui_task_mutex_);
|
||||||
|
ui_tasklist_.push_back(std::move(pt));
|
||||||
|
}
|
||||||
|
if (dispatch.notify_worker)
|
||||||
|
ui_cv_.notify_all();
|
||||||
|
}
|
||||||
|
if (dispatch.wait_for_completion)
|
||||||
|
f.get();
|
||||||
|
if (dispatch.request_redraw)
|
||||||
|
request_redraw_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ui_sync()
|
||||||
|
{
|
||||||
|
ui_task([] {});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::deque<AppTask> render_tasklist_;
|
||||||
|
std::mutex render_task_mutex_;
|
||||||
|
std::condition_variable render_cv_;
|
||||||
|
std::thread render_thread_;
|
||||||
|
std::thread::id render_thread_id_;
|
||||||
|
bool render_running_ = false;
|
||||||
|
|
||||||
|
std::deque<AppTask> ui_tasklist_;
|
||||||
|
std::mutex ui_task_mutex_;
|
||||||
|
std::condition_variable ui_cv_;
|
||||||
|
std::thread ui_thread_;
|
||||||
|
std::thread::id ui_thread_id_;
|
||||||
|
bool ui_running_ = false;
|
||||||
|
bool request_redraw_ = false;
|
||||||
|
};
|
||||||
@@ -21,6 +21,79 @@ pp::foundation::Status execute_legacy_cloud_download_selection_action(
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
class LegacyCloudWorker final {
|
||||||
|
public:
|
||||||
|
LegacyCloudWorker()
|
||||||
|
: worker_([this](std::stop_token stop_token) {
|
||||||
|
run(stop_token);
|
||||||
|
})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~LegacyCloudWorker()
|
||||||
|
{
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void post(std::function<void()> task)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
if (stopping_)
|
||||||
|
return;
|
||||||
|
tasks_.push_back(std::move(task));
|
||||||
|
}
|
||||||
|
cv_.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void shutdown()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
stopping_ = true;
|
||||||
|
}
|
||||||
|
cv_.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
void run(std::stop_token stop_token)
|
||||||
|
{
|
||||||
|
for (;;) {
|
||||||
|
std::function<void()> task;
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(mutex_);
|
||||||
|
cv_.wait(lock, [&] {
|
||||||
|
return stopping_ || stop_token.stop_requested() || !tasks_.empty();
|
||||||
|
});
|
||||||
|
if ((stopping_ || stop_token.stop_requested()) && tasks_.empty())
|
||||||
|
break;
|
||||||
|
task = std::move(tasks_.front());
|
||||||
|
tasks_.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task) {
|
||||||
|
try {
|
||||||
|
task();
|
||||||
|
} catch (...) {
|
||||||
|
LOG("cloud worker task failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::mutex mutex_;
|
||||||
|
std::condition_variable cv_;
|
||||||
|
std::deque<std::function<void()>> tasks_;
|
||||||
|
bool stopping_ = false;
|
||||||
|
std::jthread worker_;
|
||||||
|
};
|
||||||
|
|
||||||
|
LegacyCloudWorker& cloud_worker()
|
||||||
|
{
|
||||||
|
static LegacyCloudWorker worker;
|
||||||
|
return worker;
|
||||||
|
}
|
||||||
|
|
||||||
#if WITH_CURL
|
#if WITH_CURL
|
||||||
int progress_callback_download(void* clientp, curl_off_t dltotal,
|
int progress_callback_download(void* clientp, curl_off_t dltotal,
|
||||||
curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
|
curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
|
||||||
@@ -233,28 +306,12 @@ void execute_cloud_download_thread(
|
|||||||
execute_cloud_download_flow(app, request);
|
execute_cloud_download_flow(app, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
void launch_cloud_download_thread(
|
|
||||||
App& app,
|
|
||||||
const pp::app::CloudDownloadRequest& request)
|
|
||||||
{
|
|
||||||
std::thread([app = &app, request] {
|
|
||||||
execute_cloud_download_thread(*app, request);
|
|
||||||
}).detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
void execute_cloud_publish_worker(App& app, bool save_before_upload)
|
void execute_cloud_publish_worker(App& app, bool save_before_upload)
|
||||||
{
|
{
|
||||||
BT_SetTerminate();
|
BT_SetTerminate();
|
||||||
execute_cloud_publish_transfer_and_success_prompt(app, save_before_upload);
|
execute_cloud_publish_transfer_and_success_prompt(app, save_before_upload);
|
||||||
}
|
}
|
||||||
|
|
||||||
void launch_cloud_publish_thread(App& app, bool save_before_upload)
|
|
||||||
{
|
|
||||||
std::thread([app = &app, save_before_upload] {
|
|
||||||
execute_cloud_publish_worker(*app, save_before_upload);
|
|
||||||
}).detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
void wire_cloud_publish_prompt_buttons(
|
void wire_cloud_publish_prompt_buttons(
|
||||||
const std::shared_ptr<NodeMessageBox>& dialog,
|
const std::shared_ptr<NodeMessageBox>& dialog,
|
||||||
std::function<void()> upload_thread)
|
std::function<void()> upload_thread)
|
||||||
@@ -273,7 +330,9 @@ void setup_cloud_publish_prompt(App& app, bool save_before_upload)
|
|||||||
const auto prompt_plan = pp::app::plan_cloud_publish_prompt();
|
const auto prompt_plan = pp::app::plan_cloud_publish_prompt();
|
||||||
auto dialog = app.message_box(prompt_plan.title, prompt_plan.message, prompt_plan.show_cancel);
|
auto dialog = app.message_box(prompt_plan.title, prompt_plan.message, prompt_plan.show_cancel);
|
||||||
wire_cloud_publish_prompt_buttons(dialog, [&app, save_before_upload] {
|
wire_cloud_publish_prompt_buttons(dialog, [&app, save_before_upload] {
|
||||||
launch_cloud_publish_thread(app, save_before_upload);
|
queue_legacy_cloud_worker_task([app = &app, save_before_upload] {
|
||||||
|
execute_cloud_publish_worker(*app, save_before_upload);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,7 +438,9 @@ public:
|
|||||||
|
|
||||||
void start_download(const pp::app::CloudDownloadRequest& request) override
|
void start_download(const pp::app::CloudDownloadRequest& request) override
|
||||||
{
|
{
|
||||||
launch_cloud_download_thread(app_, request);
|
queue_legacy_cloud_worker_task([app = &app_, request] {
|
||||||
|
execute_cloud_download_thread(*app, request);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -395,6 +456,11 @@ void show_cloud_save_required_warning(App& app)
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
void queue_legacy_cloud_worker_task(std::function<void()> task)
|
||||||
|
{
|
||||||
|
cloud_worker().post(std::move(task));
|
||||||
|
}
|
||||||
|
|
||||||
pp::foundation::Status execute_legacy_cloud_upload_plan(
|
pp::foundation::Status execute_legacy_cloud_upload_plan(
|
||||||
App& app,
|
App& app,
|
||||||
const pp::app::CloudUploadPlan& plan)
|
const pp::app::CloudUploadPlan& plan)
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#include "app_core/document_cloud.h"
|
#include "app_core/document_cloud.h"
|
||||||
#include "foundation/result.h"
|
#include "foundation/result.h"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
class App;
|
class App;
|
||||||
class NodeDialogCloud;
|
class NodeDialogCloud;
|
||||||
|
|
||||||
@@ -22,4 +24,6 @@ namespace pp::panopainter {
|
|||||||
pp::app::CloudDownloadSelectionAction action,
|
pp::app::CloudDownloadSelectionAction action,
|
||||||
NodeDialogCloud& dialog);
|
NodeDialogCloud& dialog);
|
||||||
|
|
||||||
|
void queue_legacy_cloud_worker_task(std::function<void()> task);
|
||||||
|
|
||||||
} // namespace pp::panopainter
|
} // namespace pp::panopainter
|
||||||
|
|||||||
@@ -174,6 +174,25 @@ void close_legacy_overlay_handle_ignoring_status(
|
|||||||
(void)close_legacy_overlay_node(anchor, overlay);
|
(void)close_legacy_overlay_node(anchor, overlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void close_legacy_overlay_handle_and_reset(
|
||||||
|
Node& anchor,
|
||||||
|
pp::ui::NodeHandle& overlay) noexcept
|
||||||
|
{
|
||||||
|
if (overlay.valid()) {
|
||||||
|
close_legacy_overlay_handle_ignoring_status(anchor, overlay);
|
||||||
|
overlay = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void close_legacy_overlay_handles_and_reset(
|
||||||
|
Node& anchor,
|
||||||
|
pp::ui::NodeHandle& popup_overlay,
|
||||||
|
pp::ui::NodeHandle& tick_overlay) noexcept
|
||||||
|
{
|
||||||
|
close_legacy_overlay_handle_and_reset(anchor, popup_overlay);
|
||||||
|
close_legacy_overlay_handle_and_reset(anchor, tick_overlay);
|
||||||
|
}
|
||||||
|
|
||||||
void close_legacy_overlay_handles_if_open(
|
void close_legacy_overlay_handles_if_open(
|
||||||
Node& anchor,
|
Node& anchor,
|
||||||
const pp::foundation::Result<pp::ui::NodeHandle>& popup_overlay,
|
const pp::foundation::Result<pp::ui::NodeHandle>& popup_overlay,
|
||||||
|
|||||||
@@ -111,6 +111,13 @@ void close_legacy_popup_overlay(Node& node) noexcept;
|
|||||||
void close_legacy_overlay_handle_ignoring_status(
|
void close_legacy_overlay_handle_ignoring_status(
|
||||||
Node& anchor,
|
Node& anchor,
|
||||||
pp::ui::NodeHandle overlay) noexcept;
|
pp::ui::NodeHandle overlay) noexcept;
|
||||||
|
void close_legacy_overlay_handle_and_reset(
|
||||||
|
Node& anchor,
|
||||||
|
pp::ui::NodeHandle& overlay) noexcept;
|
||||||
|
void close_legacy_overlay_handles_and_reset(
|
||||||
|
Node& anchor,
|
||||||
|
pp::ui::NodeHandle& popup_overlay,
|
||||||
|
pp::ui::NodeHandle& tick_overlay) noexcept;
|
||||||
void close_legacy_overlay_handles_if_open(
|
void close_legacy_overlay_handles_if_open(
|
||||||
Node& anchor,
|
Node& anchor,
|
||||||
const pp::foundation::Result<pp::ui::NodeHandle>& popup_overlay,
|
const pp::foundation::Result<pp::ui::NodeHandle>& popup_overlay,
|
||||||
|
|||||||
@@ -932,8 +932,8 @@ int main(int argc, char** argv)
|
|||||||
wglMakeCurrent(NULL, NULL);
|
wglMakeCurrent(NULL, NULL);
|
||||||
|
|
||||||
running = 1;
|
running = 1;
|
||||||
App::I->render_thread_start();
|
App::I->runtime().render_thread_start(*App::I);
|
||||||
App::I->ui_thread_start();
|
App::I->runtime().ui_thread_start(*App::I);
|
||||||
|
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
glad_set_pre_callback(_pre_call_callback);
|
glad_set_pre_callback(_pre_call_callback);
|
||||||
@@ -1035,8 +1035,8 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
|
|||||||
running = 0;
|
running = 0;
|
||||||
if (hmd_renderer.joinable())
|
if (hmd_renderer.joinable())
|
||||||
hmd_renderer.join();
|
hmd_renderer.join();
|
||||||
App::I->ui_thread_stop();
|
App::I->runtime().ui_thread_stop();
|
||||||
App::I->render_thread_stop();
|
App::I->runtime().render_thread_stop();
|
||||||
App::I->terminate();
|
App::I->terminate();
|
||||||
delete App::I;
|
delete App::I;
|
||||||
PostQuitMessage(0);
|
PostQuitMessage(0);
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
void Node::app_redraw()
|
void Node::app_redraw()
|
||||||
{
|
{
|
||||||
App::I->redraw = true;
|
App::I->redraw = true;
|
||||||
App::I->render_cv.notify_all();
|
App::I->runtime().notify_render_worker();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::watch(std::function<bool(Node*)> observer)
|
void Node::watch(std::function<bool(Node*)> observer)
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ void NodeDialogCloud::init_controls()
|
|||||||
btn_cancel = find<NodeButton>("btn-cancel");
|
btn_cancel = find<NodeButton>("btn-cancel");
|
||||||
pp::panopainter::bind_legacy_click_destroys_node(*btn_cancel, *this);
|
pp::panopainter::bind_legacy_click_destroys_node(*btn_cancel, *this);
|
||||||
container = find<Node>("files-list");
|
container = find<Node>("files-list");
|
||||||
std::thread(&NodeDialogCloud::load_thumbs_thread, this).detach();
|
load_thumbs_worker_ = std::jthread(&NodeDialogCloud::load_thumbs_thread, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeDialogCloud::loaded()
|
void NodeDialogCloud::loaded()
|
||||||
@@ -79,6 +79,7 @@ void NodeDialogCloud::removed(Node* parent)
|
|||||||
{
|
{
|
||||||
NodeBorder::removed(parent);
|
NodeBorder::removed(parent);
|
||||||
closed = true;
|
closed = true;
|
||||||
|
load_thumbs_worker_.request_stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
NodeText* NodeDialogCloud::create_loading_status_text()
|
NodeText* NodeDialogCloud::create_loading_status_text()
|
||||||
@@ -143,24 +144,30 @@ std::vector<NodeDialogCloudItem*> NodeDialogCloud::create_cloud_file_items(const
|
|||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeDialogCloud::load_thumbs_thread()
|
void NodeDialogCloud::load_thumbs_thread(std::stop_token stop)
|
||||||
{
|
{
|
||||||
#if WITH_CURL
|
#if WITH_CURL
|
||||||
BT_SetTerminate();
|
BT_SetTerminate();
|
||||||
CURL *curl = curl_easy_init();
|
auto curl = std::unique_ptr<CURL, decltype(&curl_easy_cleanup)>(curl_easy_init(), curl_easy_cleanup);
|
||||||
std::string res;
|
std::string res;
|
||||||
if (curl)
|
if (curl)
|
||||||
{
|
{
|
||||||
|
if (stop.stop_requested() || closed)
|
||||||
|
return;
|
||||||
|
|
||||||
auto* text = create_loading_status_text();
|
auto* text = create_loading_status_text();
|
||||||
auto* align = text->m_parent;
|
auto* align = text->m_parent;
|
||||||
|
|
||||||
if (!load_cloud_file_list(curl, res, *text))
|
if (!load_cloud_file_list(curl.get(), res, *text))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pp::panopainter::destroy_legacy_node(*align);
|
pp::panopainter::destroy_legacy_node(*align);
|
||||||
|
|
||||||
|
if (stop.stop_requested() || closed)
|
||||||
|
return;
|
||||||
|
|
||||||
LOG("CLOUD LIST: %s", res.c_str());
|
LOG("CLOUD LIST: %s", res.c_str());
|
||||||
|
|
||||||
auto names = split(res, ',');
|
auto names = split(res, ',');
|
||||||
@@ -171,13 +178,12 @@ void NodeDialogCloud::load_thumbs_thread()
|
|||||||
{
|
{
|
||||||
const auto& n = names[i];
|
const auto& n = names[i];
|
||||||
auto* node = nodes[i];
|
auto* node = nodes[i];
|
||||||
if (closed)
|
if (stop.stop_requested() || closed)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (!load_cloud_thumb(curl, n, node, res))
|
if (!load_cloud_thumb(curl.get(), n, node, res))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
curl_easy_cleanup(curl);
|
|
||||||
}
|
}
|
||||||
#endif //CURL
|
#endif //CURL
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
#include "node_text_input.h"
|
#include "node_text_input.h"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <stop_token>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
class NodeDialogCloudItem : public NodeBorder
|
class NodeDialogCloudItem : public NodeBorder
|
||||||
{
|
{
|
||||||
@@ -46,8 +48,11 @@ public:
|
|||||||
void init_controls();
|
void init_controls();
|
||||||
virtual void loaded() override;
|
virtual void loaded() override;
|
||||||
virtual void removed(Node* parent) override;
|
virtual void removed(Node* parent) override;
|
||||||
void load_thumbs_thread();
|
void load_thumbs_thread(std::stop_token stop);
|
||||||
NodeText* create_loading_status_text();
|
NodeText* create_loading_status_text();
|
||||||
bool load_cloud_file_list(CURL* curl, std::string& response, NodeText& status_text);
|
bool load_cloud_file_list(CURL* curl, std::string& response, NodeText& status_text);
|
||||||
std::vector<NodeDialogCloudItem*> create_cloud_file_items(const std::vector<std::string>& names);
|
std::vector<NodeDialogCloudItem*> create_cloud_file_items(const std::vector<std::string>& names);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::jthread load_thumbs_worker_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -722,14 +722,10 @@ void NodePanelStroke::execute_stroke_control_plan(const pp::app::BrushStrokeCont
|
|||||||
|
|
||||||
void NodePanelStroke::close_popup_overlay_handles() noexcept
|
void NodePanelStroke::close_popup_overlay_handles() noexcept
|
||||||
{
|
{
|
||||||
if (m_popup_overlay_handle.valid()) {
|
pp::panopainter::close_legacy_overlay_handles_and_reset(
|
||||||
pp::panopainter::close_legacy_overlay_handle_ignoring_status(*this, m_popup_overlay_handle);
|
*this,
|
||||||
m_popup_overlay_handle = {};
|
m_popup_overlay_handle,
|
||||||
}
|
m_tick_overlay_handle);
|
||||||
if (m_tick_overlay_handle.valid()) {
|
|
||||||
pp::panopainter::close_legacy_overlay_handle_ignoring_status(*this, m_tick_overlay_handle);
|
|
||||||
m_tick_overlay_handle = {};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kEventResult NodePanelStroke::handle_event(Event* e)
|
kEventResult NodePanelStroke::handle_event(Event* e)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "node_popup_menu.h"
|
#include "node_popup_menu.h"
|
||||||
#include "node_button_custom.h"
|
#include "node_button_custom.h"
|
||||||
#include "app.h"
|
#include "app.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
Node* NodePopupMenu::clone_instantiate() const
|
Node* NodePopupMenu::clone_instantiate() const
|
||||||
{
|
{
|
||||||
@@ -33,6 +34,8 @@ kEventResult NodePopupMenu::handle_event(Event* e)
|
|||||||
case kEventType::MouseDownL:
|
case kEventType::MouseDownL:
|
||||||
break;
|
break;
|
||||||
case kEventType::MouseUpL:
|
case kEventType::MouseUpL:
|
||||||
|
{
|
||||||
|
auto self = std::static_pointer_cast<NodePopupMenu>(shared_from_this());
|
||||||
if (m_mouse_inside)
|
if (m_mouse_inside)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < m_children.size(); i++)
|
for (int i = 0; i < m_children.size(); i++)
|
||||||
@@ -40,12 +43,13 @@ kEventResult NodePopupMenu::handle_event(Event* e)
|
|||||||
if (m_children[i]->m_mouse_inside)
|
if (m_children[i]->m_mouse_inside)
|
||||||
{
|
{
|
||||||
if (on_select)
|
if (on_select)
|
||||||
on_select(this, i);
|
on_select(self.get(), i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
close_popup();
|
close_popup();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return kEventResult::Available;
|
return kEventResult::Available;
|
||||||
@@ -61,13 +65,19 @@ void NodePopupMenu::added(Node* parent)
|
|||||||
m_mouse_ignore = false;
|
m_mouse_ignore = false;
|
||||||
m_flood_events = true;
|
m_flood_events = true;
|
||||||
m_capture_children = false;
|
m_capture_children = false;
|
||||||
|
auto self = std::static_pointer_cast<NodePopupMenu>(shared_from_this());
|
||||||
for (int i = 0; i < m_children.size(); i++)
|
for (int i = 0; i < m_children.size(); i++)
|
||||||
{
|
{
|
||||||
if (auto b = std::dynamic_pointer_cast<NodeButtonCustom>(m_children[i]))
|
if (auto b = std::dynamic_pointer_cast<NodeButtonCustom>(m_children[i]))
|
||||||
{
|
{
|
||||||
b->on_click = [this, i](Node* target) {
|
std::weak_ptr<NodePopupMenu> weak_self = self;
|
||||||
if (on_select)
|
b->on_click = [weak_self, i](Node* target) {
|
||||||
on_select(this, i);
|
auto self = weak_self.lock();
|
||||||
|
if (!self) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (self->on_select)
|
||||||
|
self->on_select(self.get(), i);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user