Skip to content

Commit 5334384

Browse files
committed
feat: finalize portable packet and serializer runtime surface
1 parent f0be4b2 commit 5334384

49 files changed

Lines changed: 1498 additions & 602 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.MD

Lines changed: 48 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,21 @@
99
1. 给一个状态类写持久化和同步结构;
1010
2. 给一个 packet 写稳定 id、版本和 decode 逻辑;
1111
3. 给某个类型提供统一 serializer;
12-
4. 在运行时按 authored class 取回对应的 schema binding 或 packet binding
12+
4. 在运行时直接按类型保存、读取状态或 packet。
1313

14-
大多数作者平时只会接触三层东西
14+
平时常用的只有三层东西
1515

1616
1. 注解:`@PiSyncModel``@PiField``@PiPacket`
1717
2. 运行时入口:`PiSchemas``PiPackets``PiSerializeServices`
1818
3. 少量 serializer API:`PiSerializer``PiSerializers`
1919

20-
你通常不需要手写或直接依赖 `_PiSchema``_PiPacket` 这类 generated class。
20+
你通常不需要手写或直接依赖 `_PiSchema``_PiPacket` 这类 generated class,也不该把 `binding.codec()` 当作第一层日常用法。
21+
22+
常见分层可以这样理解:
23+
24+
1. `api.*` 下面是你平时直接写代码会碰到的注解和契约
25+
2. `PiSchemas``PiPackets` 是 runtime lookup 入口
26+
3. `PiNet` 会直接消费这里生成出来的 packet id、version 和 codec
2127

2228
## 典型用法
2329

@@ -34,18 +40,18 @@ public final class ManaState {
3440
}
3541
```
3642

37-
运行时你拿到的是 binding,而不是自己去找 generated class
43+
最直接的用法是按类型直接保存和读取
3844

3945
```java
40-
PiStateBinding<ManaState> binding = PiSchemas.require(ManaState.class);
41-
42-
CompoundTag full = binding.saveFull(state);
43-
binding.loadFull(restored, full, PiDecodeContext.strict());
46+
CompoundTag full = PiSchemas.saveFull(state);
47+
ManaState restored = PiSchemas.loadFull(ManaState.class, full);
4448
```
4549

46-
如果你只想写默认 client view、persisted viewdelta,也都走 binding:
50+
如果你需要 client view、persisted viewdelta 这种更细的控制,再下到 binding:
4751

4852
```java
53+
PiStateBinding<ManaState> binding = PiSchemas.require(ManaState.class);
54+
4955
CompoundTag clientView = binding.saveClientView(state);
5056
CompoundTag persisted = binding.savePersisted(state);
5157
CompoundTag delta = binding.writeClientDelta(state, dirtySet);
@@ -75,23 +81,30 @@ public final class CastSkillPacket extends PiServerPacket {
7581
this.skill = skill;
7682
this.level = level;
7783
}
78-
79-
@Override
80-
protected void handle(PiServerPacketContext context) {
81-
}
8284
}
8385
```
8486

85-
运行时同样按 authored class 或稳定 id 取 binding:
87+
大多数时候,写完这个类就够了。`PiSerializeKit` 这边主要负责让这个 packet 有稳定 id、版本和 decode 逻辑,`PiNet` 会直接接住这层结果去做注册和收发。
88+
89+
这里的 `@PiField(sync = ...)` 只是为了让 packet 字段和 schema 字段复用同一套字段描述;route、tracking、broadcast 这些发送语义还是由上层网络层决定。
90+
91+
如果你在做调试、桥接或底层 transport 接线,也可以直接按类型编解码:
8692

8793
```java
88-
PiPacketBinding<CastSkillPacket, ?> binding = PiPackets.require(CastSkillPacket.class);
89-
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
94+
CastSkillPacket packet = new CastSkillPacket("fireball", 2);
95+
FriendlyByteBuf raw = new FriendlyByteBuf(Unpooled.buffer());
96+
PiPacketBuffer buffer = PiPacketBuffers.wrap(raw);
9097

91-
binding.codec().write(buf, new CastSkillPacket("fireball", 2));
92-
CastSkillPacket decoded = binding.codec().read(buf);
98+
PiPackets.write(buffer, packet);
99+
raw.readerIndex(0);
100+
101+
ResourceLocation id = packet.packetId();
102+
int version = packet.version();
103+
CastSkillPacket decoded = PiPackets.read(CastSkillPacket.class, buffer);
93104
```
94105

106+
只有在你需要查 binding、字段列表、版本或 migration 信息时,才需要再下到 `PiPackets.require(...)`
107+
95108
### 3. 安装或临时覆盖 serializer runtime
96109

97110
如果你只是要 built-in serializer:
@@ -102,6 +115,12 @@ PiBuiltInSerializers.install(runtime);
102115
PiSerializeServices.install(runtime);
103116
```
104117

118+
这几个类的实际位置是:
119+
120+
1. `org.pickaid.piserializekit.runtime.service.PiSerializeRuntime`
121+
2. `org.pickaid.piserializekit.runtime.service.PiBuiltInSerializers`
122+
3. `org.pickaid.piserializekit.api.service.PiSerializeServices`
123+
105124
如果你只是想在某个局部逻辑里临时切换 runtime:
106125

107126
```java
@@ -112,9 +131,9 @@ PiSerializeServices.withScope(runtime, () -> {
112131

113132
这个 scoped override 很适合测试、局部覆盖或上层 pack 做隔离策略。
114133

115-
## 你实际会依赖哪些 API
134+
## 更底层时再用这些 API
116135

117-
稳定的作者侧入口主要是这些
136+
平时常用入口主要是这些
118137

119138
1. `org.pickaid.piserializekit.api.schema.*`
120139
2. `org.pickaid.piserializekit.api.packet.*`
@@ -124,49 +143,23 @@ PiSerializeServices.withScope(runtime, () -> {
124143
6. `PiPackets`
125144
7. `PiSerializeServices`
126145

127-
下面这些默认按内部实现处理,不建议下游直接写死依赖:
146+
如果你需要更多控制,再下到:
147+
148+
1. `PiStateBinding`
149+
2. `PiPacketBinding`
150+
3. `PiDecodeContext`
151+
4. `PiSerializer`
128152

129-
1. `processor.*`
130-
2. `processor.support.*`
131-
3. `processor.model.*`
132-
4. `runtime.*.support`
133-
5. `runtime.*.codec`
134-
6. generated class 名字本身
153+
这些更多是高级入口,不应该成为最先展示的日常写法。
135154

136-
## 作者侧规则
155+
## 使用规则
137156

138157
第一次写 `@PiSyncModel` / `@PiPacket` 前,先记住这些硬规则:
139158

140159
1. `@PiSyncModel.version``@PiPacket.version` 必须 `>= 1`
141160
2. schema id 和 packet id 在同一轮编译里必须唯一
142-
3. `@PiSyncModel``@PiPacket``@PiLivingService` 必须是 top-level concrete class
161+
3. `@PiSyncModel``@PiPacket` 必须是 top-level concrete class
143162
4. `@PiSyncModel` 需要可访问的无参构造器,而且不能声明 checked exception
144163
5. `@PiPacket` 的 decode 构造器必须和 `@PiField` 顺序一致,而且不能声明 checked exception
145164
6. `@PiField(serializer = ...)` provider 需要可访问的无参构造器,而且不能声明 checked exception
146165
7. `@PiAfterDecode``@PiSchemaUpgrade``@PiPacketUpgrade` 方法不能声明 checked exception
147-
148-
## 这个库不负责什么
149-
150-
这些事不属于 `PiSerializeKit`
151-
152-
1. channel 安装、发送目标、线程切换、transport guard
153-
2. capability / host runtime 绑定
154-
3. gameplay service 生命周期
155-
4. UI、render、world 的高层 author magic
156-
5. 黑盒反射式自动读写
157-
158-
换句话说:
159-
160-
1. `PiSerializeKit` 负责数据如何描述、升级、诊断、绑定
161-
2. `PiNet` 负责 packet 如何发送、路由、守卫
162-
3. `Pibrary` 和后续 packs 负责这些 schema / packet 如何进入实际 gameplay host
163-
164-
## 验证
165-
166-
这个仓库当前把下面这条命令当作最低冷验证门槛:
167-
168-
```bash
169-
bash ./gradlew clean test --no-daemon
170-
```
171-
172-
仓库里也有 `.github/workflows/ci.yml`,会在 GitHub 上跑同一条 `clean test`

README_EN.md

Lines changed: 48 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,21 @@ Use it when you need any of these:
99
1. a typed state model that can be saved and synced;
1010
2. a packet with a stable id, version, and decode path;
1111
3. a reusable serializer for one Java type;
12-
4. runtime lookup from an authored class to its generated schema or packet binding.
12+
4. direct runtime save/load helpers by type.
1313

14-
Most authors only touch three layers:
14+
Most day-to-day code only touches three layers:
1515

1616
1. annotations such as `@PiSyncModel`, `@PiField`, and `@PiPacket`;
1717
2. runtime entry points such as `PiSchemas`, `PiPackets`, and `PiSerializeServices`;
1818
3. serializer APIs such as `PiSerializer` and `PiSerializers`.
1919

20-
You normally do not hand-reference `_PiSchema`, `_PiPacket`, or other generated companion names.
20+
You normally do not hand-reference `_PiSchema`, `_PiPacket`, or other generated companion names, and the low-level `binding.codec()` path should not be the first example most users see.
21+
22+
The usual split is:
23+
24+
1. `api.*` contains the annotations and contracts you write against most of the time;
25+
2. `PiSchemas` and `PiPackets` are the runtime lookup entry points;
26+
3. `PiNet` consumes the packet ids, versions, and codecs generated here.
2127

2228
## Typical usage
2329

@@ -34,18 +40,18 @@ public final class ManaState {
3440
}
3541
```
3642

37-
At runtime you resolve the binding from the authored class:
43+
The direct path is now:
3844

3945
```java
40-
PiStateBinding<ManaState> binding = PiSchemas.require(ManaState.class);
41-
42-
CompoundTag full = binding.saveFull(state);
43-
binding.loadFull(restored, full, PiDecodeContext.strict());
46+
CompoundTag full = PiSchemas.saveFull(state);
47+
ManaState restored = PiSchemas.loadFull(ManaState.class, full);
4448
```
4549

46-
The same binding also gives you projections and deltas:
50+
If you need projections, persisted views, or deltas, then step down to the binding:
4751

4852
```java
53+
PiStateBinding<ManaState> binding = PiSchemas.require(ManaState.class);
54+
4955
CompoundTag clientView = binding.saveClientView(state);
5056
CompoundTag persisted = binding.savePersisted(state);
5157
CompoundTag delta = binding.writeClientDelta(state, dirtySet);
@@ -75,23 +81,30 @@ public final class CastSkillPacket extends PiServerPacket {
7581
this.skill = skill;
7682
this.level = level;
7783
}
78-
79-
@Override
80-
protected void handle(PiServerPacketContext context) {
81-
}
8284
}
8385
```
8486

85-
Again, runtime code resolves the binding from the authored class or stable id:
87+
Most of the time, defining the class is the main job. `PiSerializeKit` gives the packet a stable id, version, and decode path, and `PiNet` consumes that result for registration and transport.
88+
89+
The `@PiField(sync = ...)` values on packet fields only reuse the shared field descriptor model. Delivery concepts such as tracking, broadcast, and reply are still chosen by the network layer above.
90+
91+
If you are debugging, bridging another transport, or wiring low-level runtime code, you can still encode and decode it directly:
8692

8793
```java
88-
PiPacketBinding<CastSkillPacket, ?> binding = PiPackets.require(CastSkillPacket.class);
89-
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
94+
CastSkillPacket packet = new CastSkillPacket("fireball", 2);
95+
FriendlyByteBuf raw = new FriendlyByteBuf(Unpooled.buffer());
96+
PiPacketBuffer buffer = PiPacketBuffers.wrap(raw);
9097

91-
binding.codec().write(buf, new CastSkillPacket("fireball", 2));
92-
CastSkillPacket decoded = binding.codec().read(buf);
98+
PiPackets.write(buffer, packet);
99+
raw.readerIndex(0);
100+
101+
ResourceLocation id = packet.packetId();
102+
int version = packet.version();
103+
CastSkillPacket decoded = PiPackets.read(CastSkillPacket.class, buffer);
93104
```
94105

106+
Use `PiPackets.require(...)` only when you really need binding lookup, field metadata, version details, or migration details.
107+
95108
### 3. Install or scope a serializer runtime
96109

97110
If you want the built-in serializers:
@@ -102,6 +115,12 @@ PiBuiltInSerializers.install(runtime);
102115
PiSerializeServices.install(runtime);
103116
```
104117

118+
Those classes live in:
119+
120+
1. `org.pickaid.piserializekit.runtime.service.PiSerializeRuntime`
121+
2. `org.pickaid.piserializekit.runtime.service.PiBuiltInSerializers`
122+
3. `org.pickaid.piserializekit.api.service.PiSerializeServices`
123+
105124
If you want a temporary override for one code path:
106125

107126
```java
@@ -112,9 +131,9 @@ PiSerializeServices.withScope(runtime, () -> {
112131

113132
That scoped override is useful for tests, isolated local overrides, and higher-level packs that need their own serializer policy.
114133

115-
## What you should depend on
134+
## Drop lower only when needed
116135

117-
These are the main stable author-facing entry points:
136+
These are the main entry points used most of the time:
118137

119138
1. `org.pickaid.piserializekit.api.schema.*`
120139
2. `org.pickaid.piserializekit.api.packet.*`
@@ -124,49 +143,23 @@ These are the main stable author-facing entry points:
124143
6. `PiPackets`
125144
7. `PiSerializeServices`
126145

127-
These should currently be treated as internal implementation detail:
146+
If you need more control, the next layer down is:
147+
148+
1. `PiStateBinding`
149+
2. `PiPacketBinding`
150+
3. `PiDecodeContext`
151+
4. `PiSerializer`
128152

129-
1. `processor.*`
130-
2. `processor.support.*`
131-
3. `processor.model.*`
132-
4. `runtime.*.support`
133-
5. `runtime.*.codec`
134-
6. generated companion class names themselves
153+
That is the advanced path, not the main path most code should start from.
135154

136-
## Authoring rules
155+
## Usage rules
137156

138157
Before using `@PiSyncModel` or `@PiPacket`, keep these hard rules in mind:
139158

140159
1. `@PiSyncModel.version` and `@PiPacket.version` must be `>= 1`
141160
2. schema ids and packet ids must stay unique within the same compilation
142-
3. `@PiSyncModel`, `@PiPacket`, and `@PiLivingService` must be top-level concrete classes
161+
3. `@PiSyncModel` and `@PiPacket` must be top-level concrete classes
143162
4. `@PiSyncModel` needs an accessible no-arg constructor with no checked exceptions
144163
5. packet constructors used for generated decode must match `@PiField` order and must not declare checked exceptions
145164
6. `@PiField(serializer = ...)` providers need an accessible no-arg constructor with no checked exceptions
146165
7. `@PiAfterDecode`, `@PiSchemaUpgrade`, and `@PiPacketUpgrade` methods must not declare checked exceptions
147-
148-
## What this repo does not do
149-
150-
`PiSerializeKit` does not own:
151-
152-
1. channel installation, send targets, thread routing, or transport guards;
153-
2. capability or host-runtime wiring;
154-
3. gameplay service lifecycles;
155-
4. high-level UI, render, or world author magic;
156-
5. reflective black-box auto-read/write.
157-
158-
In practice:
159-
160-
1. `PiSerializeKit` owns how data is described, upgraded, diagnosed, and bound;
161-
2. `PiNet` owns how packets are transported, routed, and guarded;
162-
3. `Pibrary` and later packs own how schemas and packets enter real gameplay hosts.
163-
164-
## Verification
165-
166-
The minimum cold verification gate for this repo is:
167-
168-
```bash
169-
bash ./gradlew clean test --no-daemon
170-
```
171-
172-
The repo also ships `.github/workflows/ci.yml`, which runs the same `clean test` gate on GitHub.
Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
package org.pickaid.piserializekit.api.packet;
22

33
/**
4-
* Base type for packets that may dispatch on either side.
4+
* Base type for packets that may travel on either side.
55
*/
6-
public abstract class PiBidirectionalPacket {
6+
public abstract class PiBidirectionalPacket extends PiPacketBase {
77
public final PiPacketDirection direction() {
88
return PiPacketDirection.BIDIRECTIONAL;
99
}
10-
11-
protected abstract void handle(PiPacketContext context);
1210
}
Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
package org.pickaid.piserializekit.api.packet;
22

33
/**
4-
* Base type for clientbound packets on the common author path.
4+
* Base type for clientbound packets on the common path.
55
*/
6-
public abstract class PiClientPacket {
6+
public abstract class PiClientPacket extends PiPacketBase {
77
public final PiPacketDirection direction() {
88
return PiPacketDirection.CLIENTBOUND;
99
}
10-
11-
protected abstract void handle(PiClientPacketContext context);
1210
}

src/main/java/org/pickaid/piserializekit/api/packet/PiClientPacketContext.java

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/main/java/org/pickaid/piserializekit/api/packet/PiPacket.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* {@link PiPacketNamespace}, the path may be inferred from the class name, and
1313
* the version defaults to {@code 1}. Annotated packet types must also stay
1414
* top-level classes because generated companions are emitted as package-level
15-
* types beside the authored host. Constructors used by generated decode must
15+
* types beside the declared host. Constructors used by generated decode must
1616
* stay accessible and must not throw checked exceptions because generated
1717
* bindings instantiate packets directly.</p>
1818
*/

0 commit comments

Comments
 (0)