Skip to content

Commit 4ce2597

Browse files
committed
Add architecture and design rules for nativeapi
Introduced detailed documentation for C API bindings, global registry, native object provider, PIMPL pattern, platform implementation, project architecture, and singleton managers. These rules provide guidance for cross-platform C++/C API design, platform abstraction, memory management, and event handling, replacing the previous design-rules.md file.
1 parent 61c5931 commit 4ce2597

File tree

8 files changed

+3143
-275
lines changed

8 files changed

+3143
-275
lines changed

.cursor/rules/c-api-bindings.mdc

Lines changed: 555 additions & 0 deletions
Large diffs are not rendered by default.

.cursor/rules/global-registry.mdc

Lines changed: 555 additions & 0 deletions
Large diffs are not rendered by default.

.cursor/rules/native-object-provider.mdc

Lines changed: 474 additions & 0 deletions
Large diffs are not rendered by default.

.cursor/rules/pimpl-pattern.mdc

Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
---
2+
alwaysApply: true
3+
description: PIMPL (Pointer to Implementation) pattern for platform-specific code
4+
---
5+
6+
# PIMPL Pattern Rules
7+
8+
The nativeapi library uses the PIMPL (Pointer to Implementation) idiom extensively to hide platform-specific implementation details from the public API. This provides binary compatibility, reduces compilation dependencies, and enables clean platform abstraction.
9+
10+
## What is PIMPL?
11+
12+
PIMPL separates a class's interface from its implementation by:
13+
1. Declaring a private nested `Impl` class in the header
14+
2. Storing only a pointer to the implementation
15+
3. Implementing platform-specific logic in the source files
16+
17+
## Pattern Structure
18+
19+
### Header File Pattern ([window.h](mdc:src/window.h))
20+
21+
```cpp
22+
#pragma once
23+
#include <memory>
24+
25+
namespace nativeapi {
26+
27+
class Window {
28+
public:
29+
Window();
30+
Window(void* native_window);
31+
virtual ~Window();
32+
33+
// Public interface methods
34+
void Show();
35+
void Hide();
36+
bool IsVisible() const;
37+
38+
private:
39+
// Forward declaration only - no definition
40+
class Impl;
41+
42+
// Pointer to implementation
43+
std::unique_ptr<Impl> pimpl_;
44+
};
45+
46+
} // namespace nativeapi
47+
```
48+
49+
**Key Points:**
50+
- Forward declare `Impl` class - don't define it in header
51+
- Use `std::unique_ptr<Impl>` for automatic cleanup
52+
- No platform-specific includes in header
53+
- No platform-specific types in public interface
54+
55+
### Platform-Specific Implementation Files
56+
57+
Each platform provides its own `Impl` definition:
58+
59+
#### Windows Implementation ([platform/windows/window_windows.cpp](mdc:src/platform/windows))
60+
61+
```cpp
62+
#include <windows.h> // Platform includes only in .cpp
63+
#include "../../window.h"
64+
65+
namespace nativeapi {
66+
67+
// Define Impl class with platform-specific members
68+
class Window::Impl {
69+
public:
70+
Impl(HWND hwnd) : hwnd_(hwnd) {}
71+
72+
HWND hwnd_; // Windows-specific handle
73+
// Other Windows-specific state...
74+
};
75+
76+
Window::Window() : pimpl_(std::make_unique<Impl>(nullptr)) {}
77+
78+
Window::Window(void* window)
79+
: pimpl_(std::make_unique<Impl>(static_cast<HWND>(window))) {}
80+
81+
Window::~Window() = default; // unique_ptr handles cleanup
82+
83+
void Window::Show() {
84+
if (pimpl_->hwnd_) {
85+
ShowWindow(pimpl_->hwnd_, SW_SHOW);
86+
}
87+
}
88+
89+
} // namespace nativeapi
90+
```
91+
92+
#### macOS Implementation ([platform/macos/window_macos.mm](mdc:src/platform/macos))
93+
94+
```objc
95+
#import <Cocoa/Cocoa.h> // Platform includes only in .mm
96+
#include "../../window.h"
97+
98+
namespace nativeapi {
99+
100+
// Define Impl class with macOS-specific members
101+
class Window::Impl {
102+
public:
103+
Impl(NSWindow* window) : window_(window) {}
104+
105+
NSWindow* window_; // macOS-specific handle
106+
// Other macOS-specific state...
107+
};
108+
109+
Window::Window() : pimpl_(std::make_unique<Impl>(nil)) {}
110+
111+
Window::Window(void* window)
112+
: pimpl_(std::make_unique<Impl>(static_cast<NSWindow*>(window))) {}
113+
114+
Window::~Window() = default;
115+
116+
void Window::Show() {
117+
if (pimpl_->window_) {
118+
[pimpl_->window_ makeKeyAndOrderFront:nil];
119+
}
120+
}
121+
122+
} // namespace nativeapi
123+
```
124+
125+
#### Linux Implementation ([platform/linux/window_linux.cpp](mdc:src/platform/linux))
126+
127+
```cpp
128+
#include <gtk/gtk.h> // Platform includes only in .cpp
129+
#include "../../window.h"
130+
131+
namespace nativeapi {
132+
133+
// Define Impl class with GTK-specific members
134+
class Window::Impl {
135+
public:
136+
Impl(GtkWidget* window) : window_(window) {}
137+
138+
GtkWidget* window_; // GTK-specific handle
139+
// Other GTK-specific state...
140+
};
141+
142+
Window::Window() : pimpl_(std::make_unique<Impl>(nullptr)) {}
143+
144+
Window::Window(void* window)
145+
: pimpl_(std::make_unique<Impl>(static_cast<GtkWidget*>(window))) {}
146+
147+
Window::~Window() = default;
148+
149+
void Window::Show() {
150+
if (pimpl_->window_) {
151+
gtk_widget_show(pimpl_->window_);
152+
}
153+
}
154+
155+
} // namespace nativeapi
156+
```
157+
158+
## Implementation Guidelines
159+
160+
### 1. Creating a New PIMPL Class
161+
162+
When adding a new cross-platform class:
163+
164+
```cpp
165+
// my_class.h
166+
#pragma once
167+
#include <memory>
168+
169+
namespace nativeapi {
170+
171+
class MyClass {
172+
public:
173+
MyClass();
174+
virtual ~MyClass();
175+
176+
void DoSomething();
177+
178+
private:
179+
class Impl;
180+
std::unique_ptr<Impl> pimpl_;
181+
};
182+
183+
} // namespace nativeapi
184+
```
185+
186+
Then create implementations for each platform:
187+
- `src/platform/windows/my_class_windows.cpp`
188+
- `src/platform/macos/my_class_macos.mm`
189+
- `src/platform/linux/my_class_linux.cpp`
190+
191+
### 2. Accessing Platform State
192+
193+
Always access platform-specific members through `pimpl_`:
194+
195+
```cpp
196+
// Good
197+
void Window::SetTitle(const std::string& title) {
198+
if (pimpl_->hwnd_) { // Access through pimpl_
199+
SetWindowTextW(pimpl_->hwnd_, ...);
200+
}
201+
}
202+
203+
// Bad - won't compile, hwnd_ not in public interface
204+
void Window::SetTitle(const std::string& title) {
205+
if (hwnd_) { // Error: no member named 'hwnd_'
206+
...
207+
}
208+
}
209+
```
210+
211+
### 3. Constructor/Destructor Pattern
212+
213+
Follow this pattern for all PIMPL classes:
214+
215+
```cpp
216+
// Header
217+
class MyClass {
218+
public:
219+
MyClass();
220+
virtual ~MyClass(); // Virtual if used as base class
221+
222+
// Copy/move operations - handle appropriately
223+
MyClass(const MyClass&) = delete;
224+
MyClass& operator=(const MyClass&) = delete;
225+
226+
private:
227+
class Impl;
228+
std::unique_ptr<Impl> pimpl_;
229+
};
230+
231+
// Implementation
232+
MyClass::MyClass() : pimpl_(std::make_unique<Impl>()) {}
233+
MyClass::~MyClass() = default; // unique_ptr handles cleanup
234+
```
235+
236+
### 4. Manager Classes with PIMPL
237+
238+
Singleton managers also use PIMPL ([window_manager.h](mdc:src/window_manager.h)):
239+
240+
```cpp
241+
class WindowManager : public EventEmitter<WindowEvent> {
242+
public:
243+
static WindowManager& GetInstance();
244+
virtual ~WindowManager();
245+
246+
std::shared_ptr<Window> Create(const WindowOptions& options);
247+
248+
private:
249+
WindowManager(); // Private constructor
250+
251+
class Impl;
252+
std::unique_ptr<Impl> pimpl_;
253+
254+
// Public data that doesn't vary by platform
255+
std::unordered_map<WindowID, std::shared_ptr<Window>> windows_;
256+
};
257+
```
258+
259+
Platform setup and cleanup methods:
260+
261+
```cpp
262+
// Header
263+
private:
264+
void SetupEventMonitoring();
265+
void CleanupEventMonitoring();
266+
267+
// Implementation forwards to pimpl_
268+
WindowManager::WindowManager() : pimpl_(std::make_unique<Impl>(this)) {
269+
SetupEventMonitoring();
270+
}
271+
272+
void WindowManager::SetupEventMonitoring() {
273+
pimpl_->SetupEventMonitoring();
274+
}
275+
```
276+
277+
## Common Patterns
278+
279+
### Pattern 1: Wrapping Native Objects
280+
281+
Constructors that accept native handles ([window.h](mdc:src/window.h)):
282+
283+
```cpp
284+
// Header
285+
class Window {
286+
public:
287+
Window();
288+
Window(void* native_window); // Wrap existing native window
289+
};
290+
291+
// Windows implementation
292+
Window::Window(void* window)
293+
: pimpl_(std::make_unique<Impl>(static_cast<HWND>(window))) {}
294+
295+
// macOS implementation
296+
Window::Window(void* window)
297+
: pimpl_(std::make_unique<Impl>(static_cast<NSWindow*>(window))) {}
298+
```
299+
300+
### Pattern 2: Checking for Null Native Handles
301+
302+
Always validate before using platform handles:
303+
304+
```cpp
305+
void Window::Show() {
306+
if (!pimpl_->hwnd_) return; // Or pimpl_->window_, pimpl_->gtk_window_
307+
308+
// Safe to use handle
309+
ShowWindow(pimpl_->hwnd_, SW_SHOW);
310+
}
311+
```
312+
313+
### Pattern 3: Returning Platform Handles
314+
315+
Use `NativeObjectProvider` base class ([native_object_provider.h](mdc:src/foundation/native_object_provider.h)):
316+
317+
```cpp
318+
// Header
319+
class Window : public NativeObjectProvider {
320+
protected:
321+
void* GetNativeObjectInternal() const override;
322+
};
323+
324+
// Windows implementation
325+
void* Window::GetNativeObjectInternal() const {
326+
return static_cast<void*>(pimpl_->hwnd_);
327+
}
328+
329+
// macOS implementation
330+
void* Window::GetNativeObjectInternal() const {
331+
return static_cast<void*>(pimpl_->window_);
332+
}
333+
```
334+
335+
## Benefits of PIMPL
336+
337+
1. **Binary Compatibility** - Implementation changes don't affect public API
338+
2. **Fast Compilation** - Platform headers not included in public headers
339+
3. **Clean Separation** - Platform code completely separated
340+
4. **Type Safety** - Compiler ensures correct platform build
341+
5. **No Leaks** - `unique_ptr` handles cleanup automatically
342+
343+
## Best Practices
344+
345+
1. **Always use `std::unique_ptr<Impl>`** - Never raw pointers
346+
2. **Define destructor in .cpp file** - Even if `= default`, needed for unique_ptr of incomplete type
347+
3. **Keep public headers clean** - No platform includes, no platform types
348+
4. **Validate handles** - Check for null before using native handles
349+
5. **Use forward declarations** - Minimize includes in headers
350+
6. **Follow naming** - Always name inner class `Impl`, always name member `pimpl_`
351+
7. **Consider copy/move** - Usually delete copy, sometimes allow move
352+
8. **Document constructors** - Especially those taking native handles
353+
354+
## Related Patterns
355+
356+
- See [Native Object Provider Rules](mdc:.cursor/rules/native-object-provider.mdc) for exposing native handles
357+
- See [Platform Implementation Rules](mdc:.cursor/rules/platform-implementation.mdc) for platform-specific code organization
358+
- See [Project Architecture Rules](mdc:.cursor/rules/project-architecture.mdc) for overall structure

0 commit comments

Comments
 (0)