C#
概要
バイナリファイル(basic.bytes)と、バイナリの読み込み・マスターデータの使用を行うソースコードを出力する形式です。
使用方法
初期設定
- 設定ファイルの
output_formatにC#出力を設定します - バッチorシェルの
create_and_copy_data_windows_unity.bat/create_and_copy_data_mac_unity.shとcopy_src_unity.bat/copy_src_unity.shのコピー先を適切に設定します
手順
- バッチorシェルの
create_and_copy_data_windows_unity.batまたはcreate_and_copy_data_mac_unity.shを実行します - 初回またはyamlファイルに変更がある場合、
copy_src_unity.batまたはcopy_src_unity.shを実行します - Unity等C#を使用する側にて、WienerManager.csを使用します
使用例
- Resourcesフォルダにbasic.bytesを入れている場合
using UnityEngine;
using Wiener;
public class WienerSample : MonoBehaviour
{
public WienerManager Master { get; private set; } = new WienerManager();
void Start()
{
var textAsset = Resources.Load("basic") as TextAsset;
Master.LoadBasicPack(textAsset.bytes);
foreach (var v in Master.BasicList)
{
Debug.Log($"Id:{v.Id} Detail:{v.Detail}");
}
}
}
出力結果

YAMLのHeader定義による出力形式
SourceType
SourceTypeはマスターデータの出力方法を指定します。
Normal
データクラスの出力を行い、basic.bytesからデータを読み取ります。
Enum
enum形式の出力を行います。
Const
定数の出力を行い、データはソースコードに記載されます。
ConstSource
定数ファイルの出力を行い、データはソースコードに記載されます。
ContainerType
ContainerTypeは出力データの形式を指定します。
List
配列形式でデータの出力を行います。
value[] Name;
Dictionary
辞書形式でデータの出力を行います。
Fieldsの最初のColumnがkeyとなります。
Dictionary<key, value> Name;
DictionaryList
辞書形式でvalueが配列形式のデータ出力を行います。
Fieldsの最初のColumnがkeyとなります。
Dictionary<key, value[]> Name;
WienerDictionary
Dictionaryと同じような扱いでデータ探索を行います(Unity: 二分探索 / UE5: TMap)。
Unity では Listと同等のメモリ負荷でメモリ効率が良いですが、二分探索のため検索速度はDictionaryより劣ります。
UE5 ではブループリントで使用するためにTMapを使用します。
Fieldsの最初のColumnがkeyとなります。
WienerDictionary<key, value> Name;
WienerDictionaryList
DictionaryListと同じような扱いでデータ探索を行います(Unity: 二分探索 / UE5: TMap)。
Unity では 二次元Listと同等のメモリ負荷でメモリ効率が良いですが、二分探索のため検索速度はDictionaryListより劣ります。
UE5 ではブループリントで使用するためにTMapを使用します。
Fieldsの最初のColumnがkeyとなります。
WienerDictionaryList<key, value> Name;
C#ソースコードの使用方法
ロードやデータアクセスは全てWienerManagerクラスを通じて行います。
マスターデータのロード
LoadBasicPack
basic.bytesのロードを行います。
ファイルパスまたはバイナリを指定します。
コンバート時のソースとデータに不整合がある場合はロードに失敗します。
Result LoadBasicPack(string path);
Result LoadBasicPack(byte[] buffer);
LoadBasicPackFromHash
開発用のロードメソッドです。
basic.bytesのロードを行います。
LoadBasicPackに比べてロードは遅くなります。
ファイルパスまたはバイナリを指定します。
コンバート時のソースとデータに不整合があってもロードに成功します。
バイナリに存在してソースに存在しないデータは無視され、ソースに存在してバイナリに存在しないデータはdefault値が入りますが、難読化データはおかしな値となるので注意してください。
ロードに失敗したマスター一覧をonMissingTargetsから取得できます。
WienerManager.SetMissingDelegateでデリゲートを登録しておくことで、正常に読み込みを行えなかったマスターのアクセスを検知できます。
Result LoadBasicPackFromHash(string path, Action<string[]> onMissingTargets);
Result LoadBasicPackFromHash(byte[] buffer, Action<string[]> onMissingTargets);
マスターデータへアクセス
マスターデータのロードが完了している状態でWienerManagerの各プロパティからアクセスできます。
プロパティはHeader定義のNameで作成されます。
次のサンプルはBasicDictionaryとBasicDictionaryListを作成した時WienerManagerで作成されるプロパティを表示しています。
定義YAML
header:
...
source_type: Normal
container_type: Dictionary
name: BasicDictionary
...
header:
...
source_type: Normal
container_type: DictionaryList
name: BasicDictionaryList
...
出力されるWienerManager.cs
public partial class WienerManager
{
...
public Dictionary<int, BasicDictionaryData> BasicDictionary { get { if (BasicDictionary__ == null) onMissing?.Invoke("BasicDictionary"); return BasicDictionary__; }}
public Dictionary<int, BasicDictionaryListData[]> BasicDictionaryList { get { if (BasicDictionaryList__ == null) onMissing?.Invoke("BasicDictionaryList"); return BasicDictionaryList__; }}
...
}
その他
難読化されたデータの改ざん検知
SetFalsifyDelegateメソッドでデリゲートを登録しておくと、難読化されたデータのメモリ上の改ざんを検知できます。
void SetFalsifyDelegate(Action<string> onFalsify);
ロード時の不整合マスターデータアクセス検知
SetMissingDelegateメソッドでデリゲートを登録しておくと、 LoadBasicPackFromHashでロードした際に、正常にロードできなかったマスターデータへのアクセスを検知できます。
void SetMissingDelegate(Action<string> onMissing);
C#クラス
WienerDictionaryクラス
WienerDictionary<TKey, TValue> は、Unity 向けの省メモリな辞書風コンテナです。ソート済みデータを二分探索で検索します。UE5 ではブループリント対応のため TMap を使用します。
インデクサで存在しないキーを指定した場合は Dictionary<TKey, TValue> と同じく KeyNotFoundException を投げます。 列挙時は KeyValuePair<TKey, TValue> を返します。
namespace Wiener
{
public class WienerDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>
where TKey : IComparable
where TValue : IWienerDictionaryData<TKey>, new()
{
public int Count => values.Length;
public IEnumerable<TKey> Keys { get; }
public IEnumerable<TValue> Values { get; }
public TValue this[TKey key] { get; }
public WienerDictionary(WienerDataReader reader, WienerString[] stringList, DateTime?[] dateTimeList, bool hash = false);
public bool ContainsKey(TKey key);
public bool TryGetValue(TKey key, out TValue value);
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator();
}
}
WienerDictionaryListクラス
WienerDictionaryList<TKey, TValue> は、Unity 向けのグループ化された値を扱う省メモリな辞書風コンテナです。仕組みは Dictionary<TKey, TValue[]> とほぼ同じですが、ソート済みデータを二分探索で検索し、内部では TValue[][] だけを保持します。UE5 ではブループリント対応のため TMap を使用します。
インデクサで存在しないキーを指定した場合は Dictionary<TKey, TValue[]> と同じく KeyNotFoundException を投げます。 列挙時は KeyValuePair<TKey, TValue[]> を返します。
namespace Wiener
{
public class WienerDictionaryList<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue[]>>
where TKey : IComparable
where TValue : IWienerDictionaryData<TKey>, new()
{
public int Count => values.Length;
public IEnumerable<TKey> Keys { get; }
public IEnumerable<TValue[]> Values { get; }
public TValue[] this[TKey key] { get; }
public WienerDictionaryList(WienerDataReader reader, WienerString[] stringList, DateTime?[] dateTimeList, bool hash = false);
public bool ContainsKey(TKey key);
public bool TryGetValue(TKey key, out TValue[] value);
public IEnumerator<KeyValuePair<TKey, TValue[]>> GetEnumerator();
}
}
WienerManagerクラス
namespace Wiener
{
public class WienerManager
{
/// <summary>
/// basic.bytesの読み込みを行う
/// </summary>
/// <param name="path">basic.bytesのパスを指定</param>
/// <returns></returns>
public Result LoadBasicPack(string path);
/// <summary>
/// basic.bytesの読み込みを行う
/// </summary>
/// <param name="buffer">basic.bytesのバイナリを指定</param>
/// <returns></returns>
public Result LoadBasicPack(byte[] buffer);
/// <summary>
/// basic.bytesの読み込みを行う
///
/// このメソッドは開発中の使用を想定しています
/// Wienerは高速読み込みを行う仕組み上、ソースとバイナリが常に一対である必要がありますが
/// 開発中はソースとバイナリがずれることがあります
/// LoadBasicPackFromHashを使用すると不整合なデータは無視してロードを成功させます
/// onMissingTargetsにロードで不整合なマスター一覧が返ってくるので、ログ表示等に使用してください
/// またSetMissingDelegateメソッドでデリゲートを登録しておくことで、不整合なマスターのアクセスを検知できます
///
/// </summary>
/// <param name="path">basic.bytesのパスを指定</param>
/// <param name="onMissingTargets">ロード失敗リストを受け取るデリゲート</param>
/// <returns></returns>
public Result LoadBasicPackFromHash(string path, Action<string[]> onMissingTargets);
/// <summary>
/// basic.bytesの読み込みを行う
///
/// LoadBasicPackFromHashのバイナリ読み込み版
///
/// </summary>
/// <param name="buffer">basic.bytesのバイナリを指定</param>
/// <param name="onMissingTargets">ロード失敗リストを受け取るデリゲート</param>
/// <returns></returns>
public Result LoadBasicPackFromHash(byte[] buffer, Action<string[]> onMissingTargets);
/// <summary>
/// 難読化されたデータの改ざん検知登録
///
/// SetFalsifyDelegateメソッドでデリゲートを登録しておくと
/// 難読化されたデータがメモリ上の改ざんを検知できます
///
/// </summary>
/// <param name="onFalsify">改ざん検知デリゲートを指定</param>
public void SetFalsifyDelegate(Action<string> onFalsify);
/// <summary>
/// 非ロードマスターデータアクセス検知登録
///
/// SetMissingDelegateメソッドでデリゲートを登録しておくと
/// LoadBasicPackFromHashでロードした際に、ロードできなかったマスターへのアクセスを検知できます
///
/// </summary>
/// <param name="onMissing">非ロードマスターデータアクセス検知デリゲートを指定</param>
public void SetMissingDelegate(Action<string> onMissing);
}
}
サンプル
Basic/Basic.xlsm#辞書
| A | B | C |
|---|---|---|
| Id | Name | |
| 1 | ご飯 | |
| 2 | パン | |
| 3 | パスタ |
yaml
header:
input_path:
- path: Basic/Basic.xlsm
sheet: 辞書
row: 1
base_type: Normal
source_type: Normal
container_type: Dictionary
output_path: Basic
name: BasicDictionary
fields:
- key: Id
name: id
type: Int
- key: Name
name: name
type: String
...
出力CS
ソースコードにはマスターデータの値は含まれていないためbasic.bytesのロードが必要になります。
/// <summary>
/// Automatically generated code
/// </summary>
using System;
using System.IO;
using System.Collections.Generic;
namespace Wiener
{
public partial class BasicDictionaryData
{
public int Id { get; private set; }
public string Name => stringList[NameStringIndex].Value;
public uint GroupId { get; private set; }
WienerString[] stringList;
uint NameStringIndex;
public static (Dictionary<int, BasicDictionaryData>, bool) Create(string path, WienerString[] stringList, DateTime?[] dateTimeList)
{
var buffer = File.ReadAllBytes(path);
var reader = new WienerDataReader(buffer);
return Create(reader, stringList, dateTimeList);
}
public static (Dictionary<int, BasicDictionaryData>, bool) Create(WienerDataReader reader, WienerString[] stringList, DateTime?[] dateTimeList)
{
reader.GetKeyHashTable(false);
var length = reader.ReadInt32();
var value = new Dictionary<int, BasicDictionaryData>(length);
for (var i = 0; i < length; ++i)
{
var key = reader.ReadInt32();
var val = new BasicDictionaryData();
val.Load(reader, stringList, dateTimeList);
value.Add(key, val);
}
return (value, false);
}
public static (Dictionary<int, BasicDictionaryData>, bool) CreateFromHash(WienerDataReader reader, WienerString[] stringList, DateTime?[] dateTimeList, List<string> errorList)
{
var error = !reader.HashSeek(Hash);
if (error)
{
errorList.Add("BasicDictionaryData");
}
var hashTable = reader.GetKeyHashTable(true);
var length = reader.ReadInt32();
var value = new Dictionary<int, BasicDictionaryData>(length);
for (var i = 0; i < length; ++i)
{
var key = reader.ReadInt32();
var val = new BasicDictionaryData();
val.Load(reader, hashTable, stringList, dateTimeList);
value.Add(key, val);
}
return (value, error);
}
void Load(WienerDataReader reader, WienerString[] stringList, DateTime?[] dateTimeList)
{
this.stringList = stringList;
Id = reader.ReadInt32();
NameStringIndex = reader.ReadUInt32();
GroupId = reader.ReadUInt32();
}
private static readonly string Hash = "04ac9e6b16032778741f344174dfc928";
static readonly Dictionary<string, Action<BasicDictionaryData, WienerDataReader>> HashLoadDict;
static BasicDictionaryData()
{
HashLoadDict = new Dictionary<string, Action<BasicDictionaryData, WienerDataReader>>()
{
["462479fdd1ee4f66f2488ac46a489fff"] = LoadId,
["e3a369b99caa37cd8990e202315e8eff"] = LoadName,
["f9f3f95ae38a75495795f08f7dfdec99"] = LoadGroupId,
};
}
public void Load(WienerDataReader reader, List<(string hash, byte type)> keyHashTable, WienerString[] stringList, DateTime?[] dateTimeList)
{
this.stringList = stringList;
foreach (var v in keyHashTable)
{
if (HashLoadDict.TryGetValue(v.hash, out var action))
{
action(this, reader);
}
else
{
reader.SkipReader(v.type);
}
}
}
static void LoadId(BasicDictionaryData self, WienerDataReader reader)
{
self.Id = reader.ReadInt32();
}
static void LoadName(BasicDictionaryData self, WienerDataReader reader)
{
self.NameStringIndex = reader.ReadUInt32();
}
static void LoadGroupId(BasicDictionaryData self, WienerDataReader reader)
{
self.GroupId = reader.ReadUInt32();
}
}
}