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로 저장한다는 아이디어 내 준 게이야 복 많이 받아.
로또 당첨될거야!
해당 댓글은 삭제되었습니다.
ㅇㅇ 위에건 돌아가나 확인한거고 이제 제네릭하게 정리해봐야징
참조 저장한다는게 객체정보를 io로 저장하고 다시 불러왔을때 참조하던 다른겍체에도 영향이 되는거?
ㅇㅇ 어차피 id만 저장하니까
ㄱㅊ을거 같네 weakreferrence에 저장기능 넣은 느낌이네