morph 0.1.0
Asynchronous bridge between GUI thread and business models
Loading...
Searching...
No Matches
dev_reload.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2#pragma once
3
4#include <algorithm>
5#include <functional>
6#include <map>
7#include <memory>
8#include <span>
9#include <string>
10#include <string_view>
11#include <utility>
12#include <vector>
13
14#include "bundle.hpp"
15#include "document.hpp"
16#include "frontend.hpp"
17#include "host.hpp"
18#include "value.hpp"
19
20/// @file
21/// @brief Development reload (spec 5 §12): which documents a changed bundle changed, and remounting the mounted
22/// screens that show them, keeping their state.
23///
24/// A development client polls with `UiClient::poll` and hands each changed bundle to `reloadDocuments`. Specified in
25/// `docs/spec/ui/service.md`.
26
27namespace morph::ui {
28
29/// @brief The loaded documents a host mounts from, by screen id.
30using DocumentMap = std::map<std::string, std::shared_ptr<Document const>, std::less<>>;
31
32/// @brief The ids of the documents (screens and the app document) whose files differ between two manifests: changed,
33/// added or removed. Themes, message catalogues and components are not documents; a document that names a
34/// changed component names it by its new hash, so it changes too.
35/// @param before The manifest the client had.
36/// @param after The manifest it has now.
37/// @return The ids, sorted and each once.
38[[nodiscard]] inline std::vector<std::string> changedDocuments(Manifest const& before, Manifest const& after) {
39 auto const documents = [](Manifest const& manifest) {
40 std::map<std::string, std::string, std::less<>> hashes;
41 for (ManifestFile const& file : manifest.files) {
42 if (file.kind == kScreenFile || file.kind == kAppFile) {
43 hashes.insert_or_assign(file.id, file.hash);
44 }
45 }
46 return hashes;
47 };
48 auto const old = documents(before);
49 auto const now = documents(after);
50 std::vector<std::string> changed;
51 for (auto const& [document, hash] : now) {
52 auto const found = old.find(document);
53 if (found == old.end() || found->second != hash) {
54 changed.push_back(document);
55 }
56 }
57 for (auto const& [document, hash] : old) {
58 static_cast<void>(hash);
59 if (!now.contains(document)) {
60 changed.push_back(document);
61 }
62 }
63 std::ranges::sort(changed);
64 return changed;
65}
66
67/// @brief A mounted screen a reload may remount.
69 /// @brief The screen id it was mounted from.
70 std::string screenId;
71 /// @brief Its handle, mounted by the host the reload is given. Borrowed for the call.
72 ScreenHandle* handle = nullptr;
73};
74
75/// @brief What `reloadDocuments` did.
77 /// @brief The screen id of each screen remounted on its new document, once per screen.
78 std::vector<std::string> remounted;
79 /// @brief Each new document that did not load, and each remount the interpreter refused; the screen and the
80 /// document map keep the old document for both.
81 std::vector<Refusal> refused;
82};
83
84/// @brief Loads the changed documents of a new bundle and remounts the mounted screens that show them.
85///
86/// Each changed screen document is loaded with @p options. A document that does not load is refused and keeps the old
87/// document, in @p documents and on every screen showing it. A document that loads is mounted, through
88/// `DocumentScreenHost::remount`, on every screen of @p mounted showing it, which keeps each state of the screen's own
89/// scope declared under the same name and type. A remount the interpreter refuses keeps that screen on the old
90/// document, and keeps the old document in @p documents. A changed id the bundle no longer holds, and the app
91/// document, which no `DocumentScreenHost` mounts, change nothing here.
92/// @param changed The ids of the changed documents (`changedDocuments`).
93/// @param bundle The new bundle's documents.
94/// @param options How the documents load: the catalog facts.
95/// @param documents The host's documents, updated in place.
96/// @param host The host that mounted every screen of @p mounted.
97/// @param mounted The mounted screens.
98/// @return The screens remounted, and the refusals.
99[[nodiscard]] inline ReloadOutcome reloadDocuments(std::span<std::string const> changed, Bundle const& bundle,
100 LoadOptions const& options, DocumentMap& documents,
101 DocumentScreenHost& host, std::span<MountedScreen const> mounted) {
102 ReloadOutcome outcome;
103 for (std::string const& screenId : changed) {
104 auto const text = bundle.documents.find(screenId);
105 if (screenId == "app" || text == bundle.documents.end()) {
106 continue;
107 }
108 auto loaded = loadDocumentText(text->second, options);
109 if (!loaded) {
110 Refusal refusal = std::move(loaded.error());
111 if (refusal.document.empty()) {
112 refusal.document = screenId;
113 }
114 outcome.refused.push_back(std::move(refusal));
115 continue;
116 }
117 auto const document = std::make_shared<Document const>(std::move(*loaded));
118 bool kept = true;
119 for (MountedScreen const& screen : mounted) {
120 if (screen.screenId != screenId || screen.handle == nullptr) {
121 continue;
122 }
123 if (auto remounted = host.remount(*screen.handle, document); !remounted) {
124 outcome.refused.push_back(
125 Refusal{.document = screenId, .path = {}, .reason = std::move(remounted.error().reason)});
126 kept = false;
127 continue;
128 }
129 if (std::ranges::find(outcome.remounted, screenId) == outcome.remounted.end()) {
130 outcome.remounted.push_back(screenId);
131 }
132 }
133 if (kept) {
134 documents.insert_or_assign(screenId, document);
135 }
136 }
137 return outcome;
138}
139
140} // namespace morph::ui
What the UI service's client and server share (spec 5 §12): content hashes, the bundle's manifest,...
constexpr std::string_view kScreenFile
The file kinds of a manifest.
Definition bundle.hpp:82
constexpr std::string_view kAppFile
The app document (spec 6 §5).
Definition bundle.hpp:84
The framework's ScreenHost over UI documents: it mounts them through an Interpreter,...
Definition host.hpp:344
One mounted screen, as its host sees it.
Definition host.hpp:82
ReloadOutcome reloadDocuments(std::span< std::string const > changed, Bundle const &bundle, LoadOptions const &options, DocumentMap &documents, DocumentScreenHost &host, std::span< MountedScreen const > mounted)
Loads the changed documents of a new bundle and remounts the mounted screens that show them.
std::vector< std::string > changedDocuments(Manifest const &before, Manifest const &after)
The ids of the documents (screens and the app document) whose files differ between two manifests: cha...
std::map< std::string, std::shared_ptr< Document const >, std::less<> > DocumentMap
The loaded documents a host mounts from, by screen id.
The UI document (spec 5 §3): its model — declarations, commands, view nodes, components and customiza...
std::expected< Document, Refusal > loadDocumentText(std::string_view text, LoadOptions const &options={})
Parses JSON text as a UI document and runs its load-time validation.
The host contract (spec 6 §4): how a host mounts UI documents and closes them, whether it owns its ow...
What an open source hands the frontend: the application's screens, verified.
Definition frontend.hpp:91
std::map< std::string, std::string, std::less<> > documents
Every document of the bundle, by id, as its JSON text: the screens, the app shell,...
Definition frontend.hpp:99
Options of loadDocument.
Definition document.hpp:140
One file of a manifest.
Definition bundle.hpp:125
std::string hash
The file's content hash.
Definition bundle.hpp:131
std::string kind
screen, app, theme, messages, component or icon; a client fetches no kind it does not know.
Definition bundle.hpp:129
std::string id
The file's id: a screen's document id, app, a theme's, a catalogue's or a component's name.
Definition bundle.hpp:127
A bundle's manifest: every file the application serves, by its hash (spec 5 §12, "Content addressing"...
Definition bundle.hpp:143
A mounted screen a reload may remount.
std::string screenId
The screen id it was mounted from.
ScreenHandle * handle
Its handle, mounted by the host the reload is given. Borrowed for the call.
Why a document, an expression or a value was refused: where, and the reason.
Definition value.hpp:38
std::string document
The id of the document refused; empty when the refusal is not about a whole document.
Definition value.hpp:40
What reloadDocuments did.
std::vector< Refusal > refused
Each new document that did not load, and each remount the interpreter refused; the screen and the doc...
std::vector< std::string > remounted
The screen id of each screen remounted on its new document, once per screen.
The frontend seam: main picks a frontend at runtime, and the frontend runs whatever an AppSource open...
The UI document's typed values (ui::Value), its types (ui::Type), their JSON form,...
Node text(Text spec)
Static or bound text.
Definition view.hpp:1194