com.kylin.di.messagepack 1.0.0

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.
@@ -0,0 +1,22 @@
1
+ name: Publish to npmjs
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+
14
+ - uses: actions/setup-node@v4
15
+ with:
16
+ node-version: '20'
17
+ registry-url: 'https://registry.npmjs.org'
18
+
19
+ - name: Publish package
20
+ run: npm publish
21
+ env:
22
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## [1.0.0] - 2026-05-14
4
+
5
+ ### Added
6
+ - SubscribableProperty<T> MessagePack formatter
7
+ - SubscribableCollection<T> MessagePack formatter
8
+ - SubscribableDictionary<TKey, TValue> MessagePack formatter
9
+ - KDIMessagePackResolver with auto-registration via RuntimeInitializeOnLoadMethod
10
+ - KDIMessagePackResolver.GetOptions() for manual configuration
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kylin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # KDI MessagePack Adapter
2
+
3
+ KDI의 `SubscribableProperty`, `SubscribableCollection`, `SubscribableDictionary` 타입을 [MessagePack-CSharp](https://github.com/MessagePack-CSharp/MessagePack-CSharp)로 바이너리 직렬화할 수 있게 하는 어댑터 패키지.
4
+
5
+ ```
6
+ com.kylin.di.messagepack v1.0.0 | Unity 6000.0+ | MIT License
7
+ ```
8
+
9
+ ---
10
+
11
+ ## 설치
12
+
13
+ ### 1. Scoped Registry 추가
14
+
15
+ `Packages/manifest.json`에 다음을 추가:
16
+
17
+ ```json
18
+ {
19
+ "scopedRegistries": [
20
+ {
21
+ "name": "Kylin",
22
+ "url": "https://www.npmjs.com/~kylin",
23
+ "scopes": ["com.kylin"]
24
+ }
25
+ ],
26
+ "dependencies": {
27
+ "com.kylin.di": "1.0.0",
28
+ "com.kylin.di.messagepack": "1.0.0"
29
+ }
30
+ }
31
+ ```
32
+
33
+ ### 2. 전제 조건
34
+
35
+ 프로젝트에 MessagePack-CSharp가 설치되어 있어야 합니다. (NuGetForUnity 또는 직접 DLL 참조)
36
+
37
+ ---
38
+
39
+ ## 사용법
40
+
41
+ ### 자동 등록 (기본)
42
+
43
+ **별도 설정이 필요 없습니다.** 패키지 설치만으로 `RuntimeInitializeOnLoadMethod`를 통해 자동으로 MessagePack 기본 옵션에 KDI 리졸버가 등록됩니다.
44
+
45
+ ```csharp
46
+ // 그냥 평소처럼 직렬화하면 됩니다
47
+ var health = new SubscribableProperty<int>(100);
48
+ var bytes = MessagePackSerializer.Serialize(health);
49
+ var restored = MessagePackSerializer.Deserialize<SubscribableProperty<int>>(bytes);
50
+ // restored.Value == 100
51
+ ```
52
+
53
+ ### 데이터 클래스 예시
54
+
55
+ ```csharp
56
+ [MessagePackObject]
57
+ public class PlayerSaveData
58
+ {
59
+ [Key(0)] public SubscribableProperty<int> Health { get; set; } = new(100);
60
+ [Key(1)] public SubscribableProperty<string> Name { get; set; } = new("Player");
61
+ [Key(2)] public SubscribableCollection<string> Inventory { get; set; } = new();
62
+ [Key(3)] public SubscribableDictionary<string, int> Stats { get; set; } = new();
63
+ }
64
+
65
+ // 직렬화
66
+ var data = new PlayerSaveData();
67
+ data.Health.Value = 80;
68
+ data.Inventory.Add("Sword");
69
+ data.Stats["ATK"] = 50;
70
+
71
+ var bytes = MessagePackSerializer.Serialize(data);
72
+
73
+ // 역직렬화 - 구독 가능한 프로퍼티로 복원됨
74
+ var loaded = MessagePackSerializer.Deserialize<PlayerSaveData>(bytes);
75
+ // loaded.Health.Value == 80
76
+ // loaded.Inventory[0] == "Sword"
77
+ // loaded.Stats["ATK"] == 50
78
+
79
+ // 복원 후에도 구독이 정상 동작
80
+ loaded.Health.Subscribe(hp => Debug.Log($"HP: {hp}"));
81
+ loaded.Health.Value = 60; // "HP: 60" 출력
82
+ ```
83
+
84
+ ### 수동 옵션 구성 (선택)
85
+
86
+ 자동 등록 대신 수동으로 옵션을 구성하려면:
87
+
88
+ ```csharp
89
+ var options = KDIMessagePackResolver.GetOptions();
90
+ var bytes = MessagePackSerializer.Serialize(data, options);
91
+ var loaded = MessagePackSerializer.Deserialize<PlayerSaveData>(bytes, options);
92
+ ```
93
+
94
+ ---
95
+
96
+ ## 동작 원리
97
+
98
+ | KDI 타입 | 직렬화 형태 | 설명 |
99
+ |----------|------------|------|
100
+ | `SubscribableProperty<T>` | `T` 값 그대로 | 내부 `.Value`만 직렬화. 구독 상태는 직렬화하지 않음 |
101
+ | `SubscribableCollection<T>` | MessagePack Array | 내부 아이템 리스트를 배열로 직렬화 |
102
+ | `SubscribableDictionary<K,V>` | MessagePack Map | 내부 딕셔너리를 Map으로 직렬화 |
103
+
104
+ 역직렬화 시 새 인스턴스가 생성되며, 구독은 비어 있는 상태로 시작합니다. 이는 의도된 동작으로, 구독은 런타임에 코드로 설정하는 것이 올바른 패턴입니다.
105
+
106
+ ---
107
+
108
+ ## 의존성
109
+
110
+ - `com.kylin.di` >= 1.0.0
111
+ - MessagePack-CSharp >= 2.x
@@ -0,0 +1,107 @@
1
+ using System;
2
+ using Kylin.SubscribableProperty;
3
+ using MessagePack;
4
+ using MessagePack.Formatters;
5
+ using MessagePack.Resolvers;
6
+ using UnityEngine;
7
+
8
+ namespace Kylin.Serialization.MessagePack
9
+ {
10
+ /// <summary>
11
+ /// KDI SubscribableProperty 타입들을 MessagePack에서 직렬화할 수 있도록 하는 리졸버.
12
+ /// 런타임 초기화 시 자동으로 등록되므로 별도 설정이 필요 없다.
13
+ /// </summary>
14
+ public sealed class KDIMessagePackResolver : IFormatterResolver
15
+ {
16
+ public static readonly KDIMessagePackResolver Instance = new();
17
+
18
+ private KDIMessagePackResolver() { }
19
+
20
+ public IMessagePackFormatter<T> GetFormatter<T>()
21
+ {
22
+ return FormatterCache<T>.Formatter;
23
+ }
24
+
25
+ /// <summary>
26
+ /// 본 리졸버 + StandardResolver를 합성한 MessagePackSerializerOptions를 반환한다.
27
+ /// 수동으로 옵션을 구성할 때 사용한다.
28
+ /// </summary>
29
+ public static MessagePackSerializerOptions GetOptions()
30
+ {
31
+ var composite = CompositeResolver.Create(
32
+ Instance,
33
+ StandardResolver.Instance
34
+ );
35
+ return MessagePackSerializerOptions.Standard.WithResolver(composite);
36
+ }
37
+
38
+ /// <summary>
39
+ /// 제네릭 타입 캐시. 타입별로 한 번만 포맷터를 생성한다.
40
+ /// </summary>
41
+ private static class FormatterCache<T>
42
+ {
43
+ public static readonly IMessagePackFormatter<T> Formatter;
44
+
45
+ static FormatterCache()
46
+ {
47
+ Formatter = (IMessagePackFormatter<T>)ResolveFormatter(typeof(T));
48
+ }
49
+ }
50
+
51
+ private static object ResolveFormatter(Type type)
52
+ {
53
+ // SubscribableProperty<T>
54
+ if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(SubscribableProperty<>))
55
+ {
56
+ var innerType = type.GetGenericArguments()[0];
57
+ var formatterType = typeof(SubscribablePropertyFormatter<>).MakeGenericType(innerType);
58
+ return Activator.CreateInstance(formatterType);
59
+ }
60
+
61
+ // SubscribableCollection<T>
62
+ if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(SubscribableCollection<>))
63
+ {
64
+ var innerType = type.GetGenericArguments()[0];
65
+ var formatterType = typeof(SubscribableCollectionFormatter<>).MakeGenericType(innerType);
66
+ return Activator.CreateInstance(formatterType);
67
+ }
68
+
69
+ // SubscribableDictionary<TKey, TValue>
70
+ if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(SubscribableDictionary<,>))
71
+ {
72
+ var genericArgs = type.GetGenericArguments();
73
+ var formatterType = typeof(SubscribableDictionaryFormatter<,>).MakeGenericType(genericArgs);
74
+ return Activator.CreateInstance(formatterType);
75
+ }
76
+
77
+ return null;
78
+ }
79
+ }
80
+
81
+ /// <summary>
82
+ /// 런타임 초기화 시 KDI MessagePack 리졸버를 자동 등록한다.
83
+ /// 패키지 설치만으로 별도 코드 없이 즉시 사용 가능하다.
84
+ /// </summary>
85
+ public static class KDIMessagePackInitializer
86
+ {
87
+ private static bool _initialized;
88
+
89
+ [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
90
+ private static void Initialize()
91
+ {
92
+ if (_initialized) return;
93
+ _initialized = true;
94
+
95
+ // KDI 리졸버를 기본 리졸버 앞에 합성하여 전역 기본 옵션으로 설정
96
+ var resolver = CompositeResolver.Create(
97
+ KDIMessagePackResolver.Instance,
98
+ StandardResolver.Instance
99
+ );
100
+
101
+ MessagePackSerializer.DefaultOptions =
102
+ MessagePackSerializerOptions.Standard.WithResolver(resolver);
103
+
104
+ Debug.Log("[KDI] MessagePack 어댑터가 자동 등록되었습니다.");
105
+ }
106
+ }
107
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "Kylin.DI.MessagePack",
3
+ "rootNamespace": "Kylin.Serialization.MessagePack",
4
+ "references": [
5
+ "Kylin.DI"
6
+ ],
7
+ "includePlatforms": [],
8
+ "excludePlatforms": [],
9
+ "allowUnsafeCode": false,
10
+ "overrideReferences": false,
11
+ "precompiledReferences": [],
12
+ "autoReferenced": true,
13
+ "defineConstraints": [],
14
+ "versionDefines": [],
15
+ "noEngineReferences": false
16
+ }
@@ -0,0 +1,50 @@
1
+ using Kylin.SubscribableProperty;
2
+ using MessagePack;
3
+ using MessagePack.Formatters;
4
+
5
+ namespace Kylin.Serialization.MessagePack
6
+ {
7
+ /// <summary>
8
+ /// SubscribableCollection{T}용 MessagePack 포맷터.
9
+ /// 내부 아이템 리스트를 배열로 직렬화/역직렬화한다.
10
+ /// </summary>
11
+ public sealed class SubscribableCollectionFormatter<T> : IMessagePackFormatter<SubscribableCollection<T>>
12
+ {
13
+ public void Serialize(ref MessagePackWriter writer, SubscribableCollection<T> value,
14
+ MessagePackSerializerOptions options)
15
+ {
16
+ if (value == null)
17
+ {
18
+ writer.WriteNil();
19
+ return;
20
+ }
21
+
22
+ var formatter = options.Resolver.GetFormatterWithVerify<T>();
23
+ var count = value.Count;
24
+ writer.WriteArrayHeader(count);
25
+
26
+ for (int i = 0; i < count; i++)
27
+ {
28
+ formatter.Serialize(ref writer, value[i], options);
29
+ }
30
+ }
31
+
32
+ public SubscribableCollection<T> Deserialize(ref MessagePackReader reader,
33
+ MessagePackSerializerOptions options)
34
+ {
35
+ if (reader.TryReadNil())
36
+ return null;
37
+
38
+ var formatter = options.Resolver.GetFormatterWithVerify<T>();
39
+ var count = reader.ReadArrayHeader();
40
+ var collection = new SubscribableCollection<T>(count);
41
+
42
+ for (int i = 0; i < count; i++)
43
+ {
44
+ collection.Add(formatter.Deserialize(ref reader, options));
45
+ }
46
+
47
+ return collection;
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,57 @@
1
+ using Kylin.SubscribableProperty;
2
+ using MessagePack;
3
+ using MessagePack.Formatters;
4
+
5
+ namespace Kylin.Serialization.MessagePack
6
+ {
7
+ /// <summary>
8
+ /// SubscribableDictionary{TKey, TValue}용 MessagePack 포맷터.
9
+ /// 내부 딕셔너리를 Map으로 직렬화/역직렬화한다.
10
+ /// </summary>
11
+ public sealed class SubscribableDictionaryFormatter<TKey, TValue>
12
+ : IMessagePackFormatter<SubscribableDictionary<TKey, TValue>>
13
+ {
14
+ public void Serialize(ref MessagePackWriter writer, SubscribableDictionary<TKey, TValue> value,
15
+ MessagePackSerializerOptions options)
16
+ {
17
+ if (value == null)
18
+ {
19
+ writer.WriteNil();
20
+ return;
21
+ }
22
+
23
+ var keyFormatter = options.Resolver.GetFormatterWithVerify<TKey>();
24
+ var valueFormatter = options.Resolver.GetFormatterWithVerify<TValue>();
25
+
26
+ writer.WriteMapHeader(value.Count);
27
+
28
+ foreach (var kvp in value)
29
+ {
30
+ keyFormatter.Serialize(ref writer, kvp.Key, options);
31
+ valueFormatter.Serialize(ref writer, kvp.Value, options);
32
+ }
33
+ }
34
+
35
+ public SubscribableDictionary<TKey, TValue> Deserialize(ref MessagePackReader reader,
36
+ MessagePackSerializerOptions options)
37
+ {
38
+ if (reader.TryReadNil())
39
+ return null;
40
+
41
+ var keyFormatter = options.Resolver.GetFormatterWithVerify<TKey>();
42
+ var valueFormatter = options.Resolver.GetFormatterWithVerify<TValue>();
43
+
44
+ var count = reader.ReadMapHeader();
45
+ var dict = new SubscribableDictionary<TKey, TValue>(count);
46
+
47
+ for (int i = 0; i < count; i++)
48
+ {
49
+ var key = keyFormatter.Deserialize(ref reader, options);
50
+ var val = valueFormatter.Deserialize(ref reader, options);
51
+ dict.Add(key, val);
52
+ }
53
+
54
+ return dict;
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,37 @@
1
+ using Kylin.SubscribableProperty;
2
+ using MessagePack;
3
+ using MessagePack.Formatters;
4
+
5
+ namespace Kylin.Serialization.MessagePack
6
+ {
7
+ /// <summary>
8
+ /// SubscribableProperty{T}용 MessagePack 포맷터.
9
+ /// 내부 .Value (T) 만 직렬화/역직렬화한다.
10
+ /// </summary>
11
+ public sealed class SubscribablePropertyFormatter<T> : IMessagePackFormatter<SubscribableProperty<T>>
12
+ {
13
+ public void Serialize(ref MessagePackWriter writer, SubscribableProperty<T> value,
14
+ MessagePackSerializerOptions options)
15
+ {
16
+ if (value == null)
17
+ {
18
+ writer.WriteNil();
19
+ return;
20
+ }
21
+
22
+ var formatter = options.Resolver.GetFormatterWithVerify<T>();
23
+ formatter.Serialize(ref writer, value.Value, options);
24
+ }
25
+
26
+ public SubscribableProperty<T> Deserialize(ref MessagePackReader reader,
27
+ MessagePackSerializerOptions options)
28
+ {
29
+ if (reader.TryReadNil())
30
+ return null;
31
+
32
+ var formatter = options.Resolver.GetFormatterWithVerify<T>();
33
+ var innerValue = formatter.Deserialize(ref reader, options);
34
+ return new SubscribableProperty<T>(innerValue);
35
+ }
36
+ }
37
+ }
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "com.kylin.di.messagepack",
3
+ "version": "1.0.0",
4
+ "displayName": "KDI MessagePack Adapter",
5
+ "description": "MessagePack-CSharp formatters for KDI SubscribableProperty types. Enables binary serialization of SubscribableProperty<T>, SubscribableCollection<T>, and SubscribableDictionary<TKey,TValue>.",
6
+ "unity": "6000.0",
7
+ "license": "MIT",
8
+ "keywords": [
9
+ "messagepack",
10
+ "serialization",
11
+ "kdi"
12
+ ],
13
+ "author": {
14
+ "name": "Kylin"
15
+ },
16
+ "dependencies": {
17
+ "com.kylin.di": "1.0.0"
18
+ }
19
+ }