plusui-native-core 0.1.50 → 0.1.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Core/CMakeLists.txt +2 -3
- package/Core/Features/App/app.cpp +58 -1
- package/Core/Features/Connection/ARCHITECTURE.md +369 -0
- package/Core/Features/Connection/README.md +165 -0
- package/Core/Features/Connection/connection.cpp +57 -0
- package/Core/Features/Connection/connection.ts +186 -0
- package/Core/Features/Connection/examples/simple_tags_example.hpp +130 -0
- package/Core/Features/Connection/examples/simple_tags_example.ts +247 -0
- package/Core/Features/Window/window.cpp +187 -151
- package/Core/generated/bridge.hpp +302 -0
- package/Core/include/plusui/connection.hpp +145 -0
- package/Core/include/plusui/plusui.hpp +1 -3
- package/Core/include/plusui/window.hpp +17 -19
- package/package.json +1 -1
- package/Core/Features/Bindings/ARCHITECTURE.md +0 -328
- package/Core/Features/Bindings/CustomBindings/custom_bindings.cpp +0 -55
- package/Core/Features/Bindings/CustomBindings/custom_bindings.ts +0 -35
- package/Core/Features/Bindings/EXAMPLE_USAGE.hpp +0 -143
- package/Core/Features/Bindings/EXAMPLE_USAGE.tsx +0 -210
- package/Core/Features/Bindings/IPC_GUIDE.md +0 -325
- package/Core/Features/Bindings/NativeBindings/native_bindings.cpp +0 -30
- package/Core/Features/Bindings/NativeBindings/native_bindings.ts +0 -29
- package/Core/Features/Bindings/UNIFIED_SYSTEM.md +0 -351
- package/Core/Features/Event/Events.ts +0 -166
- package/Core/Features/Event/events.cpp +0 -200
- package/Core/include/plusui/bindings.hpp +0 -65
- package/Core/include/plusui/custom_bindings.hpp +0 -17
- package/Core/include/plusui/events.hpp +0 -58
package/Core/CMakeLists.txt
CHANGED
|
@@ -20,8 +20,6 @@ else()
|
|
|
20
20
|
endif()
|
|
21
21
|
|
|
22
22
|
add_library(plusui STATIC
|
|
23
|
-
Features/Bindings/NativeBindings/native_bindings.cpp
|
|
24
|
-
Features/Bindings/CustomBindings/custom_bindings.cpp
|
|
25
23
|
Features/App/app.cpp
|
|
26
24
|
Features/Window/window.cpp
|
|
27
25
|
Features/FileDrop/filedrop.cpp
|
|
@@ -31,12 +29,13 @@ add_library(plusui STATIC
|
|
|
31
29
|
Features/Clipboard/clipboard.cpp
|
|
32
30
|
Features/Menu/menu.cpp
|
|
33
31
|
Features/Keyboard/keyboard.cpp
|
|
34
|
-
Features/
|
|
32
|
+
Features/Connection/connection.cpp
|
|
35
33
|
Features/WebGPU/webgpu.cpp
|
|
36
34
|
)
|
|
37
35
|
|
|
38
36
|
target_include_directories(plusui PUBLIC
|
|
39
37
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
|
38
|
+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/generated>
|
|
40
39
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/vendor>
|
|
41
40
|
$<INSTALL_INTERFACE:include>
|
|
42
41
|
)
|
|
@@ -7,8 +7,31 @@
|
|
|
7
7
|
#include <plusui/tray.hpp>
|
|
8
8
|
#include <plusui/window.hpp>
|
|
9
9
|
|
|
10
|
+
#include <bridge.hpp> // Generated generated bridge
|
|
11
|
+
|
|
10
12
|
namespace plusui {
|
|
11
13
|
|
|
14
|
+
// --- Bridge Implementation (Demo) ---
|
|
15
|
+
class DemoBridge : public bridge::GeneratedBridge {
|
|
16
|
+
public:
|
|
17
|
+
nlohmann::json handle_greet(const nlohmann::json &args) override {
|
|
18
|
+
std::string name = args.value("name", "Guest");
|
|
19
|
+
return {{"message", "Hello from C++, " + name + "!"}};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
void handle_log(const nlohmann::json &args) override {
|
|
23
|
+
std::string msg = args.value("msg", "");
|
|
24
|
+
std::cout << "[Client Log] " << msg << std::endl;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
void handle_minimize(const nlohmann::json &args) override {
|
|
28
|
+
// We need access to the window to minimize it.
|
|
29
|
+
// For now, we'll just log. In a real app, Bridge might hold a reference to
|
|
30
|
+
// WindowController.
|
|
31
|
+
std::cout << "[Client] Requested minimize" << std::endl;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
12
35
|
struct App::Impl {
|
|
13
36
|
bool running = false;
|
|
14
37
|
};
|
|
@@ -109,7 +132,8 @@ Window App::Builder::build() {
|
|
|
109
132
|
// WebView configuration (now part of WindowConfig)
|
|
110
133
|
winConfig.devtools = config.devtools;
|
|
111
134
|
winConfig.scrollbars = config.scrollbars;
|
|
112
|
-
winConfig.disableWebviewDragDrop =
|
|
135
|
+
winConfig.disableWebviewDragDrop =
|
|
136
|
+
config.enableFileDrop; // Auto-disable webview drag when FileDrop enabled
|
|
113
137
|
|
|
114
138
|
// Create native window
|
|
115
139
|
auto nativeWinPtr = std::make_shared<Window>(Window::create(winConfig));
|
|
@@ -127,6 +151,39 @@ Window App::Builder::build() {
|
|
|
127
151
|
}
|
|
128
152
|
}
|
|
129
153
|
|
|
154
|
+
// --- Initialize Bridge ---
|
|
155
|
+
// Create the bridge instance
|
|
156
|
+
auto bridge = std::make_shared<DemoBridge>();
|
|
157
|
+
|
|
158
|
+
// Hook 1: Window -> Bridge (Inbound)
|
|
159
|
+
webviewWin.onMessage(
|
|
160
|
+
[bridge](const std::string &msg) { bridge->dispatch(msg); });
|
|
161
|
+
|
|
162
|
+
// Hook 2: Bridge -> Window (Outbound)
|
|
163
|
+
// We capture webviewWin by value (it's a lightweight handle)
|
|
164
|
+
bridge->setOutboundHandler([webviewWin](const std::string &msg) mutable {
|
|
165
|
+
webviewWin.postMessage(msg);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Keep bridge alive?
|
|
169
|
+
// Since webviewWin.onMessage captures 'bridge', the bridge is kept alive by
|
|
170
|
+
// the Window. And 'bridge' outbound handler captures 'webviewWin', keeping
|
|
171
|
+
// Window alive. Cycle exists, but ensures lifetime match.
|
|
172
|
+
|
|
173
|
+
// Optional: Emit an initial event
|
|
174
|
+
bridge->emit_onResize(config.width, config.height);
|
|
175
|
+
|
|
176
|
+
// Hook 3: File Drop
|
|
177
|
+
webviewWin.onFileDrop([bridge](const std::string &jsonStr) {
|
|
178
|
+
if (jsonStr.empty())
|
|
179
|
+
return;
|
|
180
|
+
try {
|
|
181
|
+
auto j = nlohmann::json::parse(jsonStr);
|
|
182
|
+
bridge->emit_onFileDrop(j);
|
|
183
|
+
} catch (...) {
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
130
187
|
return webviewWin;
|
|
131
188
|
}
|
|
132
189
|
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
# Connection Feature Architecture
|
|
2
|
+
|
|
3
|
+
## 🏗️ System Overview
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
7
|
+
│ PlusUI Connection Feature │
|
|
8
|
+
│ Unified Frontend ↔ Backend Communication │
|
|
9
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
10
|
+
|
|
11
|
+
┌──────────────────┐ ┌──────────────────┐
|
|
12
|
+
│ Frontend (TS) │ │ Backend (C++) │
|
|
13
|
+
│ │ │ │
|
|
14
|
+
│ ┌────────────┐ │ │ ┌────────────┐ │
|
|
15
|
+
│ │ connect.* │ │ │ │ Bindings │ │
|
|
16
|
+
│ │ API │ │ │ │ Class │ │
|
|
17
|
+
│ └─────┬──────┘ │ │ └─────┬──────┘ │
|
|
18
|
+
│ │ │ │ │ │
|
|
19
|
+
│ ┌─────▼──────┐ │ ┌─────────────┐ │ ┌─────▼──────┐ │
|
|
20
|
+
│ │Connection │◄─┼───►│ WebView2 │◄───────┼─►│Connection │ │
|
|
21
|
+
│ │ Client │ │ │ IPC Layer │ │ │ Protocol │ │
|
|
22
|
+
│ └────────────┘ │ └─────────────┘ │ └────────────┘ │
|
|
23
|
+
│ │ │ │
|
|
24
|
+
└──────────────────┘ └──────────────────┘
|
|
25
|
+
|
|
26
|
+
┌─────────────────┐
|
|
27
|
+
│ Schema File │
|
|
28
|
+
│ connection. │
|
|
29
|
+
│ schema │
|
|
30
|
+
└────────┬────────┘
|
|
31
|
+
│
|
|
32
|
+
┌────────▼────────┐
|
|
33
|
+
│ Bindgen │
|
|
34
|
+
│ Generator │
|
|
35
|
+
└────────┬────────┘
|
|
36
|
+
│
|
|
37
|
+
┌────────────┴────────────┐
|
|
38
|
+
│ │
|
|
39
|
+
┌──────▼─────┐ ┌──────▼──────┐
|
|
40
|
+
│bindings.ts │ │bindings.hpp │
|
|
41
|
+
│(Generated) │ │ (Generated) │
|
|
42
|
+
└────────────┘ └─────────────┘
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 📊 Communication Flow
|
|
46
|
+
|
|
47
|
+
### CALL Pattern (Request/Response)
|
|
48
|
+
```
|
|
49
|
+
Frontend Backend
|
|
50
|
+
│ │
|
|
51
|
+
│──[1] call(name, args)────►│
|
|
52
|
+
│ │
|
|
53
|
+
│ [2] handle_*
|
|
54
|
+
│ │
|
|
55
|
+
│◄──[3] result ─────────────│
|
|
56
|
+
│ │
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### FIRE Pattern (One-way)
|
|
60
|
+
```
|
|
61
|
+
Frontend Backend
|
|
62
|
+
│ │
|
|
63
|
+
│──[1] fire(name, args)────►│
|
|
64
|
+
│ │
|
|
65
|
+
│ [2] handle_*
|
|
66
|
+
│ │
|
|
67
|
+
│ (no response) │
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### EVENT Pattern (Backend → Frontend)
|
|
71
|
+
```
|
|
72
|
+
Frontend Backend
|
|
73
|
+
│ │
|
|
74
|
+
│ [1] on(name, callback) │
|
|
75
|
+
│ │
|
|
76
|
+
│ │ [2] emit_*
|
|
77
|
+
│◄──[3] event data ─────────│
|
|
78
|
+
│ │
|
|
79
|
+
│ [4] callback(data) │
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### STREAM Pattern (Continuous Data)
|
|
83
|
+
```
|
|
84
|
+
Frontend Backend
|
|
85
|
+
│ │
|
|
86
|
+
│ [1] stream.subscribe(cb) │
|
|
87
|
+
│ │
|
|
88
|
+
│ │ [2] emit_* (repeated)
|
|
89
|
+
│◄──[3] data ───────────────│
|
|
90
|
+
│◄──[4] data ───────────────│
|
|
91
|
+
│◄──[5] data ───────────────│
|
|
92
|
+
│ │
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### CHANNEL Pattern (Bidirectional Pub/Sub)
|
|
96
|
+
```
|
|
97
|
+
Frontend Backend
|
|
98
|
+
│ │
|
|
99
|
+
│──[1] channel.publish()───►│ [2] handle_*_publish
|
|
100
|
+
│ │
|
|
101
|
+
│ [3] channel.subscribe() │
|
|
102
|
+
│ │
|
|
103
|
+
│◄──[4] emit_* ─────────────│
|
|
104
|
+
│ │
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## 🔄 Build & Generation Flow
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
1. Developer writes schema
|
|
111
|
+
└─► connection.schema
|
|
112
|
+
|
|
113
|
+
2. Run bindgen tool
|
|
114
|
+
└─► node Tools/plusui-bindgen/src/advanced-bindgen.js
|
|
115
|
+
|
|
116
|
+
3. Parser reads schema
|
|
117
|
+
└─► Parses connect definitions
|
|
118
|
+
└─► Validates syntax
|
|
119
|
+
└─► Creates method definitions
|
|
120
|
+
|
|
121
|
+
4. Generator creates TypeScript
|
|
122
|
+
└─► Type definitions (GreetIn, GreetOut, etc.)
|
|
123
|
+
└─► connect.* API functions
|
|
124
|
+
└─► Typed callbacks
|
|
125
|
+
|
|
126
|
+
5. Generator creates C++
|
|
127
|
+
└─► Struct definitions with JSON converters
|
|
128
|
+
└─► Virtual handler methods
|
|
129
|
+
└─► Emit helper methods
|
|
130
|
+
└─► Message dispatcher
|
|
131
|
+
|
|
132
|
+
6. Files written
|
|
133
|
+
└─► Core/Features/Connection/generated/bindings.ts
|
|
134
|
+
└─► Core/Features/Connection/generated/bindings.hpp
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## 📦 Component Architecture
|
|
138
|
+
|
|
139
|
+
### TypeScript Side
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
┌──────────────────────────────────────┐
|
|
143
|
+
│ Generated API (bindings.ts) │
|
|
144
|
+
│ ┌────────────────────────────────┐ │
|
|
145
|
+
│ │ export const connect = { │ │
|
|
146
|
+
│ │ greet: (args) => ..., │ │
|
|
147
|
+
│ │ onResize: (cb) => ..., │ │
|
|
148
|
+
│ │ cpuUsage: { subscribe }, │ │
|
|
149
|
+
│ │ logBus: { subscribe, pub }, │ │
|
|
150
|
+
│ │ } │ │
|
|
151
|
+
│ └────────────┬───────────────────┘ │
|
|
152
|
+
└───────────────┼──────────────────────┘
|
|
153
|
+
│ uses
|
|
154
|
+
▼
|
|
155
|
+
┌──────────────────────────────────────┐
|
|
156
|
+
│ Core Connection (connection.ts) │
|
|
157
|
+
│ ┌────────────────────────────────┐ │
|
|
158
|
+
│ │ class ConnectionClient { │ │
|
|
159
|
+
│ │ call(name, args) │ │
|
|
160
|
+
│ │ fire(name, args) │ │
|
|
161
|
+
│ │ on(name, callback) │ │
|
|
162
|
+
│ │ stream(name) │ │
|
|
163
|
+
│ │ channel(name) │ │
|
|
164
|
+
│ │ } │ │
|
|
165
|
+
│ └────────────┬───────────────────┘ │
|
|
166
|
+
└───────────────┼──────────────────────┘
|
|
167
|
+
│ sends/receives
|
|
168
|
+
▼
|
|
169
|
+
┌──────────────────────────────────────┐
|
|
170
|
+
│ Message Protocol │
|
|
171
|
+
│ { │
|
|
172
|
+
│ kind: "call" | "fire" | ..., │
|
|
173
|
+
│ id: "...", │
|
|
174
|
+
│ name: "methodName", │
|
|
175
|
+
│ payload: { ... } │
|
|
176
|
+
│ } │
|
|
177
|
+
└──────────────────────────────────────┘
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### C++ Side
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
┌──────────────────────────────────────┐
|
|
184
|
+
│ User Implementation │
|
|
185
|
+
│ ┌────────────────────────────────┐ │
|
|
186
|
+
│ │ class MyApp : public │ │
|
|
187
|
+
│ │ plusui::connect::Bindings │ │
|
|
188
|
+
│ │ { │ │
|
|
189
|
+
│ │ GreetOut handle_greet(...) │ │
|
|
190
|
+
│ │ void handle_log(...) │ │
|
|
191
|
+
│ │ │ │
|
|
192
|
+
│ │ void emitUpdate() { │ │
|
|
193
|
+
│ │ emit_onResize({...}); │ │
|
|
194
|
+
│ │ } │ │
|
|
195
|
+
│ │ } │ │
|
|
196
|
+
│ └────────────┬───────────────────┘ │
|
|
197
|
+
└───────────────┼──────────────────────┘
|
|
198
|
+
│ inherits
|
|
199
|
+
▼
|
|
200
|
+
┌──────────────────────────────────────┐
|
|
201
|
+
│ Generated Base (bindings.hpp) │
|
|
202
|
+
│ ┌────────────────────────────────┐ │
|
|
203
|
+
│ │ class Bindings : public │ │
|
|
204
|
+
│ │ Connection { │ │
|
|
205
|
+
│ │ │ │
|
|
206
|
+
│ │ // Virtual handlers │ │
|
|
207
|
+
│ │ virtual GreetOut handle_* │ │
|
|
208
|
+
│ │ │ │
|
|
209
|
+
│ │ // Emit methods │ │
|
|
210
|
+
│ │ void emit_onResize(...) │ │
|
|
211
|
+
│ │ │ │
|
|
212
|
+
│ │ // Message dispatcher │ │
|
|
213
|
+
│ │ void handleMessage(Envelope) │ │
|
|
214
|
+
│ │ } │ │
|
|
215
|
+
│ └────────────┬───────────────────┘ │
|
|
216
|
+
└───────────────┼──────────────────────┘
|
|
217
|
+
│ uses
|
|
218
|
+
▼
|
|
219
|
+
┌──────────────────────────────────────┐
|
|
220
|
+
│ Core Connection (connection.hpp) │
|
|
221
|
+
│ ┌────────────────────────────────┐ │
|
|
222
|
+
│ │ class Connection { │ │
|
|
223
|
+
│ │ void send(Envelope) │ │
|
|
224
|
+
│ │ void call(...) │ │
|
|
225
|
+
│ │ void fire(...) │ │
|
|
226
|
+
│ │ void event(...) │ │
|
|
227
|
+
│ │ } │ │
|
|
228
|
+
│ └────────────────────────────────┘ │
|
|
229
|
+
└──────────────────────────────────────┘
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## 🔑 Key Design Decisions
|
|
233
|
+
|
|
234
|
+
### 1. Schema-Driven
|
|
235
|
+
- **Why**: Single source of truth, prevents TS/C++ drift
|
|
236
|
+
- **Benefit**: Auto-generated types, less manual work
|
|
237
|
+
|
|
238
|
+
### 2. Strongly Typed
|
|
239
|
+
- **Why**: Catch errors at compile time
|
|
240
|
+
- **Benefit**: Better IDE support, safer code
|
|
241
|
+
|
|
242
|
+
### 3. Pattern-Based
|
|
243
|
+
- **Why**: Different use cases need different semantics
|
|
244
|
+
- **Benefit**: Clear intent, optimized for each pattern
|
|
245
|
+
|
|
246
|
+
### 4. Code Generation
|
|
247
|
+
- **Why**: Boilerplate is tedious and error-prone
|
|
248
|
+
- **Benefit**: Consistent, maintainable, fast development
|
|
249
|
+
|
|
250
|
+
### 5. Struct-Based (not JSON)
|
|
251
|
+
- **Why**: Type safety in C++
|
|
252
|
+
- **Benefit**: Compiler checks, clear interfaces
|
|
253
|
+
|
|
254
|
+
## 🎯 Design Principles
|
|
255
|
+
|
|
256
|
+
1. **Simple Developer Experience**
|
|
257
|
+
- Minimal boilerplate
|
|
258
|
+
- Intuitive API
|
|
259
|
+
- Clear patterns
|
|
260
|
+
|
|
261
|
+
2. **Type Safety**
|
|
262
|
+
- End-to-end type checking
|
|
263
|
+
- Auto-generated types
|
|
264
|
+
- Compile-time validation
|
|
265
|
+
|
|
266
|
+
3. **Unified System**
|
|
267
|
+
- Single API for all patterns
|
|
268
|
+
- Consistent naming
|
|
269
|
+
- One import
|
|
270
|
+
|
|
271
|
+
4. **Framework Agnostic**
|
|
272
|
+
- Works with React, Vue, Svelte
|
|
273
|
+
- No framework lock-in
|
|
274
|
+
- Plain TypeScript/JavaScript
|
|
275
|
+
|
|
276
|
+
5. **Performance**
|
|
277
|
+
- Minimal overhead
|
|
278
|
+
- Efficient serialization
|
|
279
|
+
- Direct dispatch
|
|
280
|
+
|
|
281
|
+
## 📐 Message Protocol
|
|
282
|
+
|
|
283
|
+
### Envelope Structure
|
|
284
|
+
```typescript
|
|
285
|
+
{
|
|
286
|
+
kind: "call" | "fire" | "result" | "event" | "stream" |
|
|
287
|
+
"sub" | "unsub" | "publish" | "error",
|
|
288
|
+
id?: string, // Request ID (for calls)
|
|
289
|
+
name: string, // Method/event name
|
|
290
|
+
payload: unknown, // Data
|
|
291
|
+
error?: string // Error message (if kind="error")
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Message Kinds
|
|
296
|
+
- **call**: Request (needs response)
|
|
297
|
+
- **fire**: One-way command
|
|
298
|
+
- **result**: Response to call
|
|
299
|
+
- **event**: Backend notification
|
|
300
|
+
- **stream**: Continuous data
|
|
301
|
+
- **sub**: Subscribe to stream
|
|
302
|
+
- **unsub**: Unsubscribe from stream
|
|
303
|
+
- **publish**: Channel message
|
|
304
|
+
- **error**: Error response
|
|
305
|
+
|
|
306
|
+
## 🔒 Type Safety Flow
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
Schema Definition
|
|
310
|
+
↓
|
|
311
|
+
Bindgen
|
|
312
|
+
↓
|
|
313
|
+
┌──────────────┐
|
|
314
|
+
│ TypeScript │
|
|
315
|
+
│ - GreetIn │
|
|
316
|
+
│ - GreetOut │
|
|
317
|
+
└──────────────┘
|
|
318
|
+
↓
|
|
319
|
+
Frontend Code
|
|
320
|
+
await connect.greet({ name: "..." })
|
|
321
|
+
↑ Type checked!
|
|
322
|
+
↓
|
|
323
|
+
{ message: "..." }
|
|
324
|
+
↑ Type checked!
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
Schema Definition
|
|
329
|
+
↓
|
|
330
|
+
Bindgen
|
|
331
|
+
↓
|
|
332
|
+
┌──────────────┐
|
|
333
|
+
│ C++ │
|
|
334
|
+
│ - GreetIn │
|
|
335
|
+
│ - GreetOut │
|
|
336
|
+
└──────────────┘
|
|
337
|
+
↓
|
|
338
|
+
Backend Code
|
|
339
|
+
GreetOut handle_greet(const GreetIn& args)
|
|
340
|
+
↑ Type checked!
|
|
341
|
+
return { "Hello" }
|
|
342
|
+
↑ Type checked!
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## 🚀 Performance Characteristics
|
|
346
|
+
|
|
347
|
+
- **Call latency**: ~1-2ms (depends on WebView2)
|
|
348
|
+
- **Fire latency**: <1ms (fire-and-forget)
|
|
349
|
+
- **Event dispatch**: <0.1ms (in-process)
|
|
350
|
+
- **Serialization**: O(n) for payload size
|
|
351
|
+
- **Memory**: Minimal (shared memory for IPC)
|
|
352
|
+
|
|
353
|
+
## 🔧 Extension Points
|
|
354
|
+
|
|
355
|
+
Future enhancements:
|
|
356
|
+
- State synchronization (synced properties)
|
|
357
|
+
- Binary protocol option (faster than JSON)
|
|
358
|
+
- Compression for large payloads
|
|
359
|
+
- Message batching
|
|
360
|
+
- Request cancellation
|
|
361
|
+
- Middleware/interceptors
|
|
362
|
+
- RPC versioning
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
**See Also:**
|
|
367
|
+
- [README.md](README.md) - Complete guide
|
|
368
|
+
- [QUICK_REFERENCE.md](QUICK_REFERENCE.md) - Cheat sheet
|
|
369
|
+
- [MIGRATION.md](MIGRATION.md) - Migration from old system
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# PlusUI Connection Feature
|
|
2
|
+
|
|
3
|
+
## 🎯 Overview
|
|
4
|
+
|
|
5
|
+
The **Connection Feature** is PlusUI's absurdly simple system for frontend ↔ backend communication. Both sides use the same two methods: **`emit()`** and **`on()`**.
|
|
6
|
+
|
|
7
|
+
## ✨ The 5 Primitives That Cover Everything
|
|
8
|
+
|
|
9
|
+
Just `emit()` and `on()` handle all communication patterns:
|
|
10
|
+
|
|
11
|
+
| Keyword | Pattern | Direction | Use Cases |
|
|
12
|
+
|---------|---------|-----------|-----------|
|
|
13
|
+
| **EVENT** | Fire & forget | One way → | Notifications, commands, logging |
|
|
14
|
+
| **CALL** | Request/response | Two ways ↔ | Get data, save data, RPC |
|
|
15
|
+
| **STREAM** | Continuous data | One way push → | Real-time updates, monitoring |
|
|
16
|
+
| **QUERY** | Request + stream | Two way + many ↔↔↔ | Search results, paginated data |
|
|
17
|
+
| **STATE** | Synced value | Both ways ↕ | Theme, settings, shared state |
|
|
18
|
+
|
|
19
|
+
## ✨ Why It's Simple
|
|
20
|
+
|
|
21
|
+
- **Two methods**: `emit()` and `on()` - that's all
|
|
22
|
+
- **Mirror API**: Same methods on both frontend and backend
|
|
23
|
+
- **Two-way**: Frontend → Backend AND Backend → Frontend
|
|
24
|
+
- **No schema**: Write code directly, no extra files
|
|
25
|
+
- **No bindgen**: No code generation step
|
|
26
|
+
- **Total flexibility**: Design your own patterns (RPC, pub/sub, streaming, whatever)
|
|
27
|
+
|
|
28
|
+
## 🚀 Quick Start
|
|
29
|
+
|
|
30
|
+
### Send Message →
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { connect } from 'plusui-native-core/connection';
|
|
34
|
+
|
|
35
|
+
// Listen for response
|
|
36
|
+
connect.on('greetResponse', (data) => {
|
|
37
|
+
console.log(data.message); // "Hello, World!"
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Send message to backend
|
|
41
|
+
connect.emit('greet', { name: 'World' });
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Receive & Respond
|
|
45
|
+
|
|
46
|
+
```cpp
|
|
47
|
+
#include <plusui/connection.hpp>
|
|
48
|
+
|
|
49
|
+
class MyConnection : public plusui::Connection {
|
|
50
|
+
protected:
|
|
51
|
+
void handleMessage(const Envelope& env) override {
|
|
52
|
+
// Listen for messages from frontend
|
|
53
|
+
if (env.kind == MessageKind::Fire && env.name == "greet") {
|
|
54
|
+
std::string name = env.payload["name"];
|
|
55
|
+
|
|
56
|
+
// Respond by emitting back
|
|
57
|
+
emit("greetResponse", {{"message", "Hello, " + name + "!"}});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public:
|
|
62
|
+
// Emit messages to frontend
|
|
63
|
+
void emit(const std::string& name, const nlohmann::json& data) {
|
|
64
|
+
event(name, data);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Example: notify frontend of window resize
|
|
68
|
+
void onWindowResize(int w, int h) {
|
|
69
|
+
emit("resize", {{"width", w}, {"height", h}});
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Listen on Frontend
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// Listen for messages from backend
|
|
78
|
+
connect.on('resize', (data) => {
|
|
79
|
+
console.log(`${data.width}x${data.height}`);
|
|
80
|
+
updateLayout(data);
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 📖 Complete API
|
|
85
|
+
|
|
86
|
+
### Frontend
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// Send message
|
|
90
|
+
connect.emit('messageName', { key: 'value' });
|
|
91
|
+
|
|
92
|
+
// Listen for messages
|
|
93
|
+
connect.on('messageName', (data) => {
|
|
94
|
+
console.log(data);
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Backend
|
|
99
|
+
|
|
100
|
+
```cpp
|
|
101
|
+
// Listen for messages (in handleMessage)
|
|
102
|
+
void handleMessage(const Envelope& env) override {
|
|
103
|
+
if (env.kind == MessageKind::Fire && env.name == "messageName") {
|
|
104
|
+
// process env.payload
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Send messages
|
|
109
|
+
void emit(const std::string& name, const nlohmann::json& data) {
|
|
110
|
+
event(name, data);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 💡 Design Your Own Patterns
|
|
115
|
+
|
|
116
|
+
### Request/Response Pattern
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
// Frontend
|
|
120
|
+
connect.on('getUserResponse', (data) => {
|
|
121
|
+
console.log(data.user);
|
|
122
|
+
});
|
|
123
|
+
connect.emit('getUser', { id: 123 });
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```cpp
|
|
127
|
+
// Backend
|
|
128
|
+
void handleMessage(const Envelope& env) override {
|
|
129
|
+
if (env.name == "getUser") {
|
|
130
|
+
auto user = database.findUser(env.payload["id"]);
|
|
131
|
+
emit("getUserResponse", {{"user", user}});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Notifications
|
|
137
|
+
|
|
138
|
+
```cpp
|
|
139
|
+
// Backend sends
|
|
140
|
+
emit("notification", {{"text", "File saved!"}});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
// Frontend receives
|
|
145
|
+
connect.on('notification', (data) => {
|
|
146
|
+
toast.success(data.text);
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Bidirectional Chat
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// Both sides listen
|
|
154
|
+
connect.on('chat', (data) => {
|
|
155
|
+
addToLog(data.message);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Both sides send
|
|
159
|
+
connect.emit('chat', { message: 'Hello!' });
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## 🎯 That's It
|
|
163
|
+
|
|
164
|
+
Two methods. Both sides. Use them however you want.
|
|
165
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#include <plusui/connection.hpp>
|
|
2
|
+
|
|
3
|
+
namespace plusui {
|
|
4
|
+
|
|
5
|
+
// Connection implementation
|
|
6
|
+
//
|
|
7
|
+
// The Connection class is header-only (see plusui/connection.hpp).
|
|
8
|
+
// This file is reserved for future non-template implementation details.
|
|
9
|
+
//
|
|
10
|
+
// TWO METHODS. FIVE PRIMITIVES. EVERYTHING YOU NEED.
|
|
11
|
+
//
|
|
12
|
+
// To use Connection:
|
|
13
|
+
// 1. Create a class that inherits from plusui::Connection
|
|
14
|
+
// 2. Override handleMessage(name, payload) to receive messages
|
|
15
|
+
// 3. Use emit(name, payload) to send messages to frontend
|
|
16
|
+
//
|
|
17
|
+
// Example - All 5 Primitives:
|
|
18
|
+
//
|
|
19
|
+
// class MyApp : public plusui::Connection {
|
|
20
|
+
// protected:
|
|
21
|
+
// void handleMessage(const std::string& name, const nlohmann::json& data) override {
|
|
22
|
+
// // EVENT: fire & forget
|
|
23
|
+
// if (name == "notification") {
|
|
24
|
+
// showNotification(data["message"].get<std::string>());
|
|
25
|
+
// }
|
|
26
|
+
//
|
|
27
|
+
// // CALL: request/response
|
|
28
|
+
// if (name == "getUser") {
|
|
29
|
+
// auto userId = data["id"].get<int>();
|
|
30
|
+
// emit("userData", {{"name", "John"}, {"id", userId}});
|
|
31
|
+
// }
|
|
32
|
+
//
|
|
33
|
+
// // STREAM: continuous updates (start streaming)
|
|
34
|
+
// if (name == "subscribeCPU") {
|
|
35
|
+
// startCPUMonitor(); // Will call emit("cpu", ...) repeatedly
|
|
36
|
+
// }
|
|
37
|
+
//
|
|
38
|
+
// // QUERY: request + stream responses
|
|
39
|
+
// if (name == "search") {
|
|
40
|
+
// auto query = data["query"].get<std::string>();
|
|
41
|
+
// for (auto& result : searchDatabase(query)) {
|
|
42
|
+
// emit("searchResult", result); // Multiple results
|
|
43
|
+
// }
|
|
44
|
+
// }
|
|
45
|
+
//
|
|
46
|
+
// // STATE: synced value
|
|
47
|
+
// if (name == "theme") {
|
|
48
|
+
// currentTheme = data["mode"].get<std::string>();
|
|
49
|
+
// emit("theme", data); // Sync back to frontend
|
|
50
|
+
// }
|
|
51
|
+
// }
|
|
52
|
+
// };
|
|
53
|
+
//
|
|
54
|
+
// See Core/Features/Connection/README.md for more details.
|
|
55
|
+
|
|
56
|
+
}
|
|
57
|
+
|