Skip to content

Commit 23e6273

Browse files
committed
Unify object identity/lifecycle model
Display becomes an identity object: managed via shared_ptr, non-copyable, identified by an integer DisplayId from IdAllocator (GetName() keeps the human-readable string). DisplayManager now caches one live instance per connected display keyed by platform identity, so repeated enumeration returns the same object with a stable id; the cache/diff/event logic is shared code and platforms only implement EnumerateNativeDisplays(). GetAll()/GetPrimary() return shared_ptr; GetPrimary() returns nullptr instead of throwing (the library's only throwing API). DisplayEvent carries a shared_ptr<Display>; DisplayChangedEvent no longer carries old/new snapshots - identity objects read state live. Shortcut is non-copyable to match the identity-object rules. The identity-object / value-object rules are specified in specs/object-model.md.
1 parent e9e97a3 commit 23e6273

23 files changed

Lines changed: 484 additions & 435 deletions

examples/display_c_example/main.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,8 @@ int main() {
2626
printf(" Name: %s\n", name ? name : "Unknown");
2727
free_c_str(name);
2828

29-
char* id = native_display_get_id(display);
30-
printf(" ID: %s\n", id ? id : "Unknown");
31-
free_c_str(id);
29+
native_display_id_t id = native_display_get_id(display);
30+
printf(" ID: %u\n", id);
3231

3332
native_point_t position = native_display_get_position(display);
3433
printf(" Position: (%.0f, %.0f)\n", position.x, position.y);

examples/display_example/main.cpp

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ int main() {
3333
DisplayManager& displayManager = DisplayManager::GetInstance();
3434

3535
// Test getting all displays
36-
std::vector<Display> displays = displayManager.GetAll();
36+
std::vector<std::shared_ptr<Display>> displays = displayManager.GetAll();
3737

3838
if (!displays.empty()) {
3939
std::cout << "Found " << displays.size() << " display(s):" << std::endl << std::endl;
4040

4141
for (size_t i = 0; i < displays.size(); i++) {
42-
const Display& display = displays[i];
42+
const Display& display = *displays[i];
4343

4444
std::cout << "Display " << (i + 1) << ":" << std::endl;
4545

@@ -88,11 +88,15 @@ int main() {
8888

8989
// Test getting primary display
9090
std::cout << "=== Primary Display ===" << std::endl;
91-
Display primary = displayManager.GetPrimary();
92-
std::cout << "Primary display: " << primary.GetName() << std::endl;
91+
std::shared_ptr<Display> primary = displayManager.GetPrimary();
92+
if (primary) {
93+
std::cout << "Primary display: " << primary->GetName() << std::endl;
9394

94-
Size size = primary.GetSize();
95-
std::cout << "Size: " << (int)size.width << " x " << (int)size.height << std::endl;
95+
Size size = primary->GetSize();
96+
std::cout << "Size: " << (int)size.width << " x " << (int)size.height << std::endl;
97+
} else {
98+
std::cout << "No primary display available" << std::endl;
99+
}
96100

97101
// Test getting cursor position
98102
std::cout << std::endl << "=== Cursor Position ===" << std::endl;

examples/window_example/main.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -320,11 +320,11 @@ int main() {
320320

321321
display_manager.AddListener<nativeapi::DisplayAddedEvent>(
322322
[](const nativeapi::DisplayAddedEvent& event) {
323-
std::cout << "Display added: " << event.GetDisplay().GetId() << std::endl;
323+
std::cout << "Display added: " << event.GetDisplay()->GetId() << std::endl;
324324
});
325325
display_manager.AddListener<nativeapi::DisplayRemovedEvent>(
326326
[](const nativeapi::DisplayRemovedEvent& event) {
327-
std::cout << "Display removed: " << event.GetDisplay().GetId() << std::endl;
327+
std::cout << "Display removed: " << event.GetDisplay()->GetId() << std::endl;
328328
});
329329

330330
auto& app = Application::GetInstance();

specs/object-model.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# 对象模型规范:身份对象与值对象
2+
3+
> 状态:已实施
4+
> 适用范围:`src/` 全部公共类型,以及 codegen 生成的 C ABI 与各语言绑定
5+
6+
本规范回答一个问题:**库中的一个公共类型,应该以什么方式被创建、持有、
7+
传递和销毁。**每个公共类型必须归入下面两类之一;新增类型时先决定归属,
8+
再写代码。
9+
10+
## 1. 分类总览
11+
12+
| | 身份对象 | 值对象 |
13+
|---|---|---|
14+
| 代表什么 | 一个唯一的底层资源 | 一段纯数据 |
15+
| 拷贝 | 禁止(拷贝"身份"没有意义) | 自由拷贝 |
16+
| 持有方式 | `std::shared_ptr` | 按值 |
17+
| 标识 | 整数 ID(若被集合管理) ||
18+
| 状态 | 从底层资源活读,不做快照 | 自身即状态 |
19+
| 跨 C ABI | handle(见 [handle-ownership.md](../docs/handle-ownership.md)| 转换为对应的 C struct |
20+
21+
## 2. 身份对象(identity object)
22+
23+
代表一个唯一的底层资源:一个窗口、一个托盘图标、一个物理显示器、
24+
一次快捷键注册。
25+
26+
### 规则
27+
28+
1. **`std::shared_ptr` 管理和传递。**类内 `delete` 拷贝构造、拷贝赋值、
29+
移动构造、移动赋值四件套,并在注释中说明"share the shared_ptr instead"。
30+
2. **被 manager/registry 以集合管理的类型持有一个整数 ID。**
31+
- 类型别名在该类型自己的头文件中定义一次:
32+
`typedef IdAllocator::IdType XxxId;`
33+
- ID 在实例构造时从 `IdAllocator::Allocate<T>()` 分配,之后不变;
34+
`GetId()` 返回它。
35+
- 类型必须先在 `foundation/id_allocator.h``IdTypeTag<T>` 注册表中
36+
登记 tag(**只可追加,不可改号**);漏登记是编译错误。
37+
3. **同一底层资源的重复查询必须返回同一个实例。**manager 负责按底层
38+
资源的平台身份做实例缓存与去重;实例存活期间 ID 因而稳定。
39+
4. **属性活读。**getter 每次从底层资源读取当前状态;持有的实例永远反映
40+
现状,不保存快照。底层资源消失后 getter 返回类型默认值。
41+
5. **生命周期与底层资源解耦但单向感知。**资源消失(如显示器拔出)时
42+
实例从 manager 缓存移除,已被外部持有的 `shared_ptr` 仍安全可用,
43+
只是读到默认值;同一资源重新出现得到**新实例、新 ID**
44+
45+
### 成员
46+
47+
`Window``TrayIcon``Menu``MenuItem``Shortcut``Display``Image`
48+
49+
### 无集合 ID 的身份对象
50+
51+
`Preferences``SecureStorage``LaunchAtLogin``KeyboardMonitor`
52+
`MessageDialog` 等实例类:同样禁拷贝、以 handle 跨 ABI,但不进入任何
53+
manager 集合,因此不定义 `XxxId` 别名、不调用 `IdAllocator::Allocate`
54+
它们仍需要 `IdTypeTag` 登记——那只服务于 handle 表的类型校验。
55+
56+
## 3. 值对象(value object)
57+
58+
纯数据,没有底层资源身份。
59+
60+
### 规则
61+
62+
1. 可自由拷贝;需要相等性时按成员值比较。
63+
2. 不进 handle 表;跨 C ABI 时按值转换为对应的 C struct。
64+
3. 不持有 ID,不注册 `IdTypeTag`
65+
66+
### 成员
67+
68+
- 几何与外观:`Point``Size``Rectangle``Color`
69+
- 输入描述:`KeyboardAccelerator`
70+
- options 类:`ShortcutOptions``WindowOptions`
71+
- 全部 Event 类(见第 5 节)
72+
73+
## 4. 案例:Display 的归属
74+
75+
`Display` 曾是可拷贝值类型(pimpl 深拷贝、平台字符串做 ID),是身份对象
76+
规则的最佳反例,现按本规范归入身份对象:
77+
78+
- `DisplayId`(整数)在实例创建时分配;人类可读名称保留在 `GetName()`
79+
- `DisplayManager`**平台身份 key**(macOS 的 `CGDirectDisplayID`
80+
Windows 的设备名等)缓存实例。key 是私有实现细节,不出现在公共 API。
81+
- 显示器保持连接期间,`GetAll()` / `GetPrimary()` 每次返回同一
82+
`shared_ptr`,ID 稳定;断开后实例从缓存移除(`DisplayRemovedEvent`
83+
携带最后一份引用),重新连接得到新实例、新 ID。
84+
- 平台层只实现原生枚举(`EnumerateNativeDisplays()`);缓存、diff、
85+
事件发射是共享代码(`display_manager.cpp`)。
86+
- `DisplayChangedEvent` 只携带发生变化的 display 本身,不携带 old/new
87+
两份"快照"——身份对象属性活读,旧状态快照本就无法成立(规则 4 的
88+
直接推论)。
89+
90+
新类型拿不准归属时,对照这个案例:**"两个实例可能指同一个东西吗?"**
91+
可能——身份对象;不可能——值对象。
92+
93+
## 5. 事件中的对象引用
94+
95+
Event 类本身是值对象(按值构造、跨线程传递、进回调),但它可以引用
96+
身份对象。允许两种形态:
97+
98+
- 携带 `std::shared_ptr<T>`(如 `DisplayEvent`):事件让对象多活一程,
99+
适合"资源即将消失、监听者还需要读它"的场景(如 removed 事件)。
100+
- 只携带整数 ID(如 `WindowEvent`):监听者按需通过 manager 解析,
101+
适合对象必然还活着的场景。
102+
103+
事件**不得**按值内嵌身份对象(那要求身份对象可拷贝,与第 2 节矛盾)。
104+
105+
## 6. 新增类型检查单
106+
107+
- [ ] 决定归属:身份对象还是值对象?
108+
- [ ] 身份对象:删除四件套拷贝/移动;`IdTypeTag` 登记;
109+
需要集合管理时定义 `XxxId` 并在构造时分配。
110+
- [ ] 身份对象:确定谁负责实例去重(哪个 manager、按什么平台 key)。
111+
- [ ] 值对象:确认无 ID、无 handle、C ABI 有对应 struct 映射。
112+
- [ ] 事件引用身份对象时,选 `shared_ptr` 或 ID,不按值内嵌。

src/capi/display_c.cpp

Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,36 +17,26 @@
1717
#include "geometry_c.h"
1818
#include "../display.h"
1919

20-
native_display_t native_display_create(void) {
21-
try {
22-
return nativeapi::HandleTable::GetInstance().Insert(
23-
std::make_shared<nativeapi::Display>());
24-
} catch (...) {
25-
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_display_create");
26-
return 0;
27-
}
28-
}
29-
30-
native_display_t native_display_create_with_display(void* display) {
20+
native_display_t native_display_create(void* display) {
3121
try {
3222
return nativeapi::HandleTable::GetInstance().Insert(
3323
std::make_shared<nativeapi::Display>(display));
3424
} catch (...) {
35-
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_display_create_with_display");
25+
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_display_create");
3626
return 0;
3727
}
3828
}
3929

40-
char* native_display_get_id(native_display_t display) {
30+
native_display_id_t native_display_get_id(native_display_t display) {
4131
auto self = nativeapi::HandleTable::GetInstance().Resolve<nativeapi::Display>(display);
4232
if (!self) {
43-
return nullptr;
33+
return 0;
4434
}
4535
try {
46-
return to_c_str(self->GetId());
36+
return self->GetId();
4737
} catch (...) {
4838
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_display_get_id");
49-
return nullptr;
39+
return 0;
5040
}
5141
}
5242

@@ -216,8 +206,7 @@ bool to_c_display_event(const nativeapi::DisplayEvent& event, native_display_eve
216206
return false;
217207
}
218208
*out = native_display_event_t{};
219-
out->display = nativeapi::HandleTable::GetInstance().Insert(
220-
std::make_shared<nativeapi::Display>(event.GetDisplay()));
209+
out->display = nativeapi::HandleTable::GetInstance().Insert(event.GetDisplay());
221210
if (const auto* typed = dynamic_cast<const nativeapi::DisplayAddedEvent*>(&event)) {
222211
out->type = NATIVE_DISPLAY_EVENT_TYPE_ADDED;
223212
(void)typed;
@@ -230,10 +219,7 @@ bool to_c_display_event(const nativeapi::DisplayEvent& event, native_display_eve
230219
}
231220
if (const auto* typed = dynamic_cast<const nativeapi::DisplayChangedEvent*>(&event)) {
232221
out->type = NATIVE_DISPLAY_EVENT_TYPE_CHANGED;
233-
out->data.changed.old_display = nativeapi::HandleTable::GetInstance().Insert(
234-
std::make_shared<nativeapi::Display>(typed->GetOldDisplay()));
235-
out->data.changed.new_display = nativeapi::HandleTable::GetInstance().Insert(
236-
std::make_shared<nativeapi::Display>(typed->GetNewDisplay()));
222+
(void)typed;
237223
return true;
238224
}
239225
return false;
@@ -245,11 +231,5 @@ void free_c_display_event(native_display_event_t* value) {
245231
}
246232
nativeapi::HandleTable::GetInstance().Release(value->display);
247233
value->display = 0;
248-
if (value->type == NATIVE_DISPLAY_EVENT_TYPE_CHANGED) {
249-
nativeapi::HandleTable::GetInstance().Release(value->data.changed.old_display);
250-
value->data.changed.old_display = 0;
251-
nativeapi::HandleTable::GetInstance().Release(value->data.changed.new_display);
252-
value->data.changed.new_display = 0;
253-
}
254234
}
255235

src/capi/display_c.h

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
extern "C" {
2020
#endif
2121

22+
typedef unsigned int native_display_id_t;
23+
2224
typedef enum {
2325
NATIVE_DISPLAY_ORIENTATION_PORTRAIT = 0,
2426
NATIVE_DISPLAY_ORIENTATION_LANDSCAPE = 90,
@@ -57,27 +59,16 @@ typedef enum {
5759
typedef struct {
5860
native_display_event_type_t type;
5961
native_display_t display;
60-
union {
61-
struct {
62-
native_display_t old_display;
63-
native_display_t new_display;
64-
} changed;
65-
} data;
6662
} native_display_event_t;
6763

6864
typedef void (*native_display_event_callback_t)(const native_display_event_t* event, void* user_data);
6965

7066
/// Creates a Display instance; release it with native_display_free().
7167
FFI_PLUGIN_EXPORT
72-
native_display_t native_display_create(void);
68+
native_display_t native_display_create(void* display);
7369

74-
/// Creates a Display instance; release it with native_display_free().
75-
FFI_PLUGIN_EXPORT
76-
native_display_t native_display_create_with_display(void* display);
77-
78-
/// Caller owns the returned string; free it with free_c_str().
7970
FFI_PLUGIN_EXPORT
80-
char* native_display_get_id(native_display_t display);
71+
native_display_id_t native_display_get_id(native_display_t display);
8172

8273
/// Caller owns the returned string; free it with free_c_str().
8374
FFI_PLUGIN_EXPORT

src/capi/display_manager_c.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ native_display_list_t native_display_manager_get_all(void) {
3131
return list;
3232
}
3333
for (size_t i = 0; i < items.size(); ++i) {
34-
list.displays[i] = nativeapi::HandleTable::GetInstance().Insert(
35-
std::make_shared<nativeapi::Display>(items[i]));
34+
list.displays[i] = nativeapi::HandleTable::GetInstance().Insert(items[i]);
3635
}
3736
list.count = static_cast<long>(items.size());
3837
return list;
@@ -45,8 +44,7 @@ native_display_list_t native_display_manager_get_all(void) {
4544

4645
native_display_t native_display_manager_get_primary(void) {
4746
try {
48-
return nativeapi::HandleTable::GetInstance().Insert(
49-
std::make_shared<nativeapi::Display>(nativeapi::DisplayManager::GetInstance().GetPrimary()));
47+
return nativeapi::HandleTable::GetInstance().Insert(nativeapi::DisplayManager::GetInstance().GetPrimary());
5048
} catch (...) {
5149
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_display_manager_get_primary");
5250
return 0;

0 commit comments

Comments
 (0)