Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
query_service.hpp
Go to the documentation of this file.
1#pragma once
2
3// QueryService — runs sample-library-scale index/search work OFF the UI thread
4// and marshals the results back as JSON strings for delivery on the UI thread.
5//
6// This is the native "index/service the JS queries" model chosen for R7 (see
7// planning/2026-07-06-valdi-lessons-for-pulp.md §4). Rather than standing up a
8// second JS heap on a worker thread — which would be engine-specific and would
9// need a deep-copy value bridge Pulp does not have — heavy data work is done in
10// C++ on a background thread and only the result (a JSON string of matched item
11// indices) crosses back to the UI thread.
12//
13// Thread-safety contract (the important part):
14// - The worker thread only ever touches copied item text, an immutable
15// already-built SearchIndex (via a shared_ptr), and the caller-supplied
16// DeliverFn. It has NO access to the view tree, the JS engine, or audio.
17// - DeliverFn is invoked FROM the worker thread and must itself be
18// thread-safe. The bridge implementation pushes the result onto a
19// mutex-guarded queue that the UI thread drains once per frame; the actual
20// hand-off into JS happens entirely on the UI thread.
21// - The service owns a single BackgroundJobService worker, so builds and
22// queries for a dataset are serialized; a built index is never mutated
23// after publication, so concurrent const queries are safe.
24//
25// Layering: the reusable, view-independent primitive is `runtime::SearchIndex`.
26// QueryService lives in `view/` on purpose — it is the bridge-facing
27// orchestrator whose contract (a `callback_id` string + a JSON-string payload)
28// is shaped for the JS bridge that is its only consumer. A non-bridge caller
29// wanting off-thread search should compose `runtime::SearchIndex` +
30// `runtime::BackgroundJobService` directly rather than reach for this glue.
31
34
35#include <cstddef>
36#include <functional>
37#include <map>
38#include <memory>
39#include <mutex>
40#include <string>
41#include <vector>
42
43namespace pulp::view {
44
46 std::size_t max_results = 500;
47 bool fuzzy = true;
48 bool case_sensitive = false;
49};
50
52public:
59 using DeliverFn =
60 std::function<void(const std::string& callback_id, std::string json_payload)>;
61
62 explicit QueryService(DeliverFn deliver);
64
65 QueryService(const QueryService&) = delete;
67
76 void build(std::string dataset_id, std::vector<std::string> items,
77 std::string callback_id = {});
78
83 void query(std::string dataset_id, std::string query_text,
84 std::string callback_id, QueryOptions options = {});
85
87 void release(const std::string& dataset_id);
88
90 std::size_t dataset_count() const;
91
92private:
93 // Registry shared with worker jobs (captured by shared_ptr) so a detached
94 // job can safely read an index even if the service is torn down. Guarded by
95 // its own mutex; touched by the single worker thread (build install /
96 // query lookup) and the UI thread (release / count).
97 struct Registry {
98 mutable std::mutex mutex;
99 std::map<std::string, std::shared_ptr<const runtime::SearchIndex>> indices;
100
101 std::shared_ptr<const runtime::SearchIndex> get(const std::string& id) const;
102 // Install `index` for `id` unless `cancel` is already signalled, both
103 // checked under the registry lock. release() cancels then erase()s under
104 // the same lock, so a build racing a release can never resurrect a
105 // released dataset: whichever takes the lock first, the dataset is gone
106 // once both complete. Returns true if the index was installed.
107 bool put_unless_cancelled(const std::string& id,
108 std::shared_ptr<const runtime::SearchIndex> index,
110 void erase(const std::string& id);
111 std::size_t size() const;
112 };
113
114 DeliverFn deliver_;
115 std::shared_ptr<Registry> registry_;
117
118 // Latest in-flight job handle per dataset, for supersession/cancellation.
119 // Touched only on the UI thread (build/query/release are UI-thread entry
120 // points). Queries supersede queries; builds supersede builds; release
121 // cancels both. Query and build for one dataset are tracked separately so a
122 // query never cancels a pending build it depends on.
123 std::map<std::string, runtime::BackgroundJobHandle> active_queries_;
124 std::map<std::string, runtime::BackgroundJobHandle> active_builds_;
125};
126
127} // namespace pulp::view
Definition background_job.hpp:87
Definition cancel.hpp:77
QueryService(const QueryService &)=delete
QueryService & operator=(const QueryService &)=delete
void query(std::string dataset_id, std::string query_text, std::string callback_id, QueryOptions options={})
Search dataset_id off the UI thread.
void release(const std::string &dataset_id)
Drop a dataset's index. Frees the memory once no in-flight job holds it.
void build(std::string dataset_id, std::vector< std::string > items, std::string callback_id={})
Build (or replace) the index for dataset_id off the UI thread from items.
QueryService(DeliverFn deliver)
std::size_t dataset_count() const
Number of datasets with a currently-installed index (UI-thread view).
std::function< void(const std::string &callback_id, std::string json_payload)> DeliverFn
Delivers a completed result.
Definition query_service.hpp:59
Hover-cursor tracking for platform window hosts.
Definition fwd.hpp:27
@ cancel
Definition window_classes.hpp:126
Definition query_service.hpp:45
bool fuzzy
Definition query_service.hpp:47
std::size_t max_results
0 = unlimited
Definition query_service.hpp:46
bool case_sensitive
Definition query_service.hpp:48