1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Newtonsoft.Json;
using NUnit.Framework;
using Zenject;
namespace Assembly_CSharpTests.PDCTest.Native
{
///
/// 객체들을 싹 들고 있는 객체 저장소. 객체 생성시에 쓸 ID도 들고있음.
///
internal class ObjectContainer
{
public int CurrentManId;
public int CurrentWeaponId;
public List<Man> ManList = new List<Man>();
public List<Weapon> WeaponList = new List<Weapon>();
}
internal interface ISaveIDHas
{
int ID { get; }
}
internal class Man : ISaveIDHas
{
[Inject] private ObjectContainer _objectContainer;
public string Name;
public int WeaponId;
public int ID { get; set; }
[JsonIgnore] public Weapon Weapon => _objectContainer.WeaponList[WeaponId];
//장비할 때 서로의 ID를 설정해준다.
//무기 입장에서는 주인의 정보만 저장함.(순환되지 않게)
public void Equip(Weapon gunForMan)
{
WeaponId = gunForMan.ID;
Weapon.Equipped(this);
}
public int GetDamage()
{
return Weapon.Damage;
}
public class Factory : Factory<Man>
{
public Man CreateWithID(ObjectContainer container)
{
var newMan = Create();
newMan.ID = container.CurrentManId;
container.ManList.Add(newMan);
container.CurrentManId++;
return newMan;
}
}
}
internal class Weapon : ISaveIDHas
{
[Inject] private ObjectContainer _objectContainer;
public int Damage;
public int OwnerManId;
public int ID { get; set; }
[JsonIgnore] public Man Owner => _objectContainer.ManList[OwnerManId];
public void Equipped(Man man)
{
OwnerManId = man.ID;
}
public class Factory : Factory<Weapon>
{
public Weapon CreateWithID(ObjectContainer container)
{
var newWeapon = Create();
newWeapon.ID = container.CurrentWeaponId;
container.WeaponList.Add(newWeapon);
container.CurrentWeaponId++;
return newWeapon;
}
}
}
internal class Test_ReferenceSaveLoad : ZenjectUnitTestFixture
{
[Inject] public Man.Factory MansFactory;
[Inject] public Weapon.Factory WeaponFactory;
[Inject] private ObjectContainer _objectContainer;
[SetUp]
public void Install()
{
Container.Bind<ObjectContainer>().AsSingle();
Container.BindFactory<Man, Man.Factory>().FromNew();
Container.BindFactory<Weapon, Weapon.Factory>().FromNew();
Container.Inject(this);
}
[Test]
public void Test_Performance_ListGetByIndex_VS_GetField()
{
//많아봤자 1만개 될 일도 잘 없지...
int length = 10000;
for (int i = 0; i < length; i++)
{
WeaponFactory.CreateWithID(_objectContainer);
}
Console.WriteLine($"{_objectContainer.WeaponList.Count}");
//리스트에서 꺼내오기(동적 참조)의 부하측정
//O(1) 이므로 느릴 이유가 없다.
var stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < length; i++)
{
var found = _objectContainer.WeaponList[i];
}
stopwatch.Stop();
Console.WriteLine($"using List index {length} loops, {stopwatch.ElapsedMilliseconds}ms!");
//미리 찾아두기 (보통 필드변수 사용)시의 부하측정
//하나마나지만...
stopwatch.Reset();
stopwatch.Start();
var freeFound = _objectContainer.WeaponList[1];
for (int i = 0; i < length; i++)
{
var found = freeFound;
}
stopwatch.Stop();
Console.WriteLine($"using freeFound, {length} loops, {stopwatch.ElapsedMilliseconds}ms!");
}
[Test]
public void Test_SaveRefID()
{
var dummyMan = MansFactory.CreateWithID(_objectContainer); //아이디가 0인 사람
var tomeMan = MansFactory.CreateWithID(_objectContainer); //아이디가 1인 사람
var dummyGun = WeaponFactory.CreateWithID(_objectContainer); //아이디가0인 무기
var goodGun = WeaponFactory.CreateWithID(_objectContainer); //아이디가1인 무기
goodGun.Damage = 10;
//총을 들려준다.참조연결 (로드할 때는 필요하지 않음) (그러나 그래픽 변경등을 하겠다면 필요할 수 있지)
tomeMan.Equip(goodGun);
tomeMan.Name = "Tome";
//세이브 프로세스
var json = JsonConvert.SerializeObject(_objectContainer);
Console.WriteLine(json);
//로드프로세스
_objectContainer = JsonConvert.DeserializeObject<ObjectContainer>(json, new JsonSerializerSettings
{
ContractResolver = new ZenjectContractResolver(Container)
});
//로드된 오브젝트 저장소에 객체 개수 확인.
Assert.AreEqual(2, _objectContainer.ManList.Count);
Assert.AreEqual(2, _objectContainer.WeaponList.Count);
//사람을 하나 꺼내 객체 복각 상태와 링크를 확인한다.
var loadedTome = _objectContainer.ManList[1];
Assert.AreEqual(1, loadedTome.Weapon.ID);
Assert.AreEqual("Tome", loadedTome.Name);
Assert.AreEqual(10, loadedTome.GetDamage());
//무기도 주인의 정보를 가지고 있음.
Assert.AreEqual(loadedTome.ID, loadedTome.Weapon.Owner.ID);
}
}
}
cs



젠젝트 +Json.net + 참조저장 성공.


ID를 통한 통짜 세이브 로드 성공했다.

저장되는 객체들은 전부 저장소의 리스트에 ID 로 관리되고,

다른 객체와 관계를 맺을 때 (참조를 딸때)는 ID만 따로 저장하는 식.


사용법 : 쉬움

보통
Man.Weapon = gun;
이라고 쓸 것을
Man.WeaponId = gun.Id;
로 써주는 것으로 끝.

참조객체를 불러쓸 때는 실시간으로 (=>) 저장소에서 ID로 찾아씀.


성능 : 문제없음.

저장소의 List에서 index 로 불러 쓰는 것이므로, 부하는 필드에 참조 따놓는거랑 차이 없음.
(C# list는 내부가 배열이라서 짱빠름)

(속도차이가 날 수가 없지...)


세이브/로드 용이성 : 아주쉬움

세이브 할 때는 걍 ObjectStorage만 통째로 시리얼/디시리얼 해주면 끝.

복각된 객체들은 다른 객체들의 ID를 당연히 들고 있음.(int니까!) 그래서 언제든 동적으로 참조를 찾아 쓰면 됨.


{

"CurrentManId":2,"

CurrentWeaponId":2,"

ManList":[{"Name":null,"WeaponId":0,"ID":0},{"Name":"Tome","WeaponId":1,"ID":1}],

"WeaponList":[{"Damage":0,"OwnerManId":0,"ID":0},{"Damage":10,"OwnerManId":1,"ID":1}]

}


감동! 이 깔끔한 Json을 보라! 이렇게 깔끔한 Json본게 얼마 만이던가!


(근데 나중에는 바이너리로 바꿀생각임. json 겁나 무겁고요...)



단점 : 객체를 처음 생성할 때 문법이 살짝 까다로움. 그렇다고 ObjectStorage 에게 생성권한을 줘서는 책임소재 문제가 생김.


안티패턴 냄새가 살짝 나지만


ObjectStorage에 책임을 최대한 적게 지울려고 노력하면 되지 않을까.











ID로 저장한다는 아이디어 내 준 게이야 복 많이 받아.


로또 당첨될거야!