├── .gitignore ├── Editor.meta ├── Editor ├── FlexBufferBrowser.cs ├── FlexBufferBrowser.cs.meta ├── FlexBuffersPreferences.cs ├── FlexBuffersPreferences.cs.meta ├── FlxQuery.cs ├── FlxQuery.cs.meta ├── FlxQueryParser.cs ├── FlxQueryParser.cs.meta ├── ImportAsFlexBuffer.cs ├── ImportAsFlexBuffer.cs.meta ├── Unity.FlexBuffers.Editor.asmdef └── Unity.FlexBuffers.Editor.asmdef.meta ├── LICENSE ├── LICENSE.meta ├── README.md ├── README.md.meta ├── Runtime.meta ├── Runtime ├── BitWidth.cs ├── BitWidth.cs.meta ├── CsvToFlexBufferConverter.cs ├── CsvToFlexBufferConverter.cs.meta ├── FlexBuffer.cs ├── FlexBuffer.cs.meta ├── FlexBufferBuilder.cs ├── FlexBufferBuilder.cs.meta ├── FlxValue.cs ├── FlxValue.cs.meta ├── JsonToFlexBufferConverter.cs ├── JsonToFlexBufferConverter.cs.meta ├── StackValue.cs ├── StackValue.cs.meta ├── Types.cs ├── Types.cs.meta ├── Unity.FlexBuffers.asmdef ├── Unity.FlexBuffers.asmdef.meta ├── XmlToFlexBufferConverter.cs └── XmlToFlexBufferConverter.cs.meta ├── package.json └── package.json.meta /.gitignore: -------------------------------------------------------------------------------- 1 | [Ll]ibrary/ 2 | [Tt]emp/ 3 | [Oo]bj/ 4 | [Bb]uild/ 5 | [Bb]uilds/ 6 | Assets/AssetStoreTools* 7 | 8 | # Visual Studio cache directory 9 | .vs/ 10 | 11 | # Autogenerated VS/MD/Consulo solution and project files 12 | ExportedObj/ 13 | .consulo/ 14 | *.csproj 15 | *.unityproj 16 | *.sln 17 | *.suo 18 | *.tmp 19 | *.user 20 | *.userprefs 21 | *.pidb 22 | *.booproj 23 | *.svd 24 | *.pdb 25 | *.opendb 26 | 27 | # Unity3D generated meta files 28 | *.pidb.meta 29 | *.pdb.meta 30 | 31 | # Unity3D Generated File On Crash Reports 32 | sysinfo.txt 33 | 34 | # Builds 35 | *.apk 36 | *.unitypackage 37 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f2653a97ec80d438197caa3cfc025774 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/FlexBufferBrowser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using FlexBuffers; 5 | using UnityEditor; 6 | using UnityEditor.IMGUI.Controls; 7 | using UnityEngine; 8 | using FlexBuffers; 9 | 10 | public class FlexBufferTreeWindow : EditorWindow 11 | { 12 | private TreeViewState _treeViewState; 13 | 14 | private FlexBufferTreeView _treeView; 15 | private string _path = ""; 16 | private int _fileSize; 17 | private string _jsonPath = ""; 18 | private int _jsonFileSize; 19 | private string _query = ""; 20 | 21 | void OnEnable () 22 | { 23 | // Check whether there is already a serialized view state (state 24 | // that survived assembly reloading) 25 | if (_treeViewState == null) 26 | _treeViewState = new TreeViewState (); 27 | _path = ""; 28 | _jsonPath = ""; 29 | _query = ""; 30 | _fileSize = 0; 31 | } 32 | 33 | void OnGUI () 34 | { 35 | if (GUILayout.Button("Open FlexBuffer file...")) 36 | { 37 | var jsonPath = EditorUtility.OpenFilePanel("Select FlexBuffer file", "", "bytes"); 38 | if (jsonPath.Length == 0) 39 | { 40 | return; 41 | } 42 | 43 | _path = jsonPath; 44 | 45 | _query = ""; 46 | 47 | var bytes = File.ReadAllBytes(_path); 48 | 49 | _fileSize = bytes.Length; 50 | 51 | var root = FlxValue.FromBytes(bytes); 52 | _treeView = new FlexBufferTreeView(root, _treeViewState); 53 | _treeViewState = new TreeViewState (); 54 | } 55 | 56 | if (_path.Length == 0) 57 | { 58 | return; 59 | } 60 | 61 | var jsonFileString = _jsonPath.Length > 0 ? $"| {_jsonPath} [{_jsonFileSize}]" : ""; 62 | GUILayout.Label($"{_path} [{_fileSize}] {jsonFileString}"); 63 | 64 | if (_jsonPath.Length > 0) 65 | { 66 | GUILayout.BeginHorizontal(); 67 | } 68 | 69 | if (GUILayout.Button("Export as JSON...")) 70 | { 71 | var jsonPath = EditorUtility.SaveFilePanel( 72 | "Save as JSON", 73 | "", 74 | $"{Path.GetFileNameWithoutExtension(_path)}.json", 75 | "json"); 76 | 77 | if (jsonPath.Length != 0) 78 | { 79 | var prettyJson = FlexBuffersPreferences.PrettyPrintedJson ? _treeView._rootValue.ToPrettyJson() : _treeView._rootValue.ToJson; 80 | _jsonFileSize = prettyJson.Length; 81 | File.WriteAllText(jsonPath, prettyJson); 82 | } 83 | 84 | _jsonPath = jsonPath; 85 | } 86 | 87 | if (_jsonPath.Length > 0) 88 | { 89 | if (GUILayout.Button("Open JSON")) 90 | { 91 | Application.OpenURL($"file://{_jsonPath}"); 92 | } 93 | 94 | if (GUILayout.Button("Import from JSON")) 95 | { 96 | var bytes = JsonToFlexBufferConverter.ConvertFile(_jsonPath); 97 | 98 | if (bytes != null) 99 | { 100 | File.WriteAllBytes(_path, bytes); 101 | } 102 | 103 | var root = FlxValue.FromBytes(bytes); 104 | _treeView = new FlexBufferTreeView(root, _treeViewState); 105 | _treeViewState = new TreeViewState (); 106 | } 107 | GUILayout.EndHorizontal(); 108 | } 109 | 110 | var newQuery = GUILayout.TextField(_query); 111 | 112 | if (newQuery != _query) 113 | { 114 | var query = FlxQueryParser.Convert(newQuery); 115 | _query = newQuery; 116 | _treeView?.SetQuery(query); 117 | } 118 | 119 | _treeView?.OnGUI(new Rect(0, 80, position.width, position.height - 80)); 120 | 121 | 122 | if (Event.current.type == EventType.KeyUp && (Event.current.modifiers == EventModifiers.Control || 123 | Event.current.modifiers == EventModifiers.Command)) 124 | { 125 | if (Event.current.keyCode == KeyCode.C) 126 | { 127 | Event.current.Use(); 128 | var selection = _treeView?.GetSelection(); 129 | if (selection != null && selection.Count > 0) 130 | { 131 | if (_treeView.GetRows().First(item => item.id == selection[0]) is FlxValueTreeViewItem row) 132 | { 133 | GUIUtility.systemCopyBuffer = row.FlxValue.ToJson; 134 | } 135 | } 136 | } 137 | } 138 | } 139 | 140 | [MenuItem("Tools/FlexBuffers/FlexBuffer Browser")] 141 | private static void ShowWidow() 142 | { 143 | var window = GetWindow (); 144 | window.titleContent = new GUIContent ("FlexBuffer Browser"); 145 | window.Show (); 146 | } 147 | } 148 | 149 | public class FlexBufferTreeView : TreeView 150 | { 151 | internal readonly FlxValue _rootValue; 152 | private FlxQuery _query; 153 | 154 | internal void SetQuery(FlxQuery query) 155 | { 156 | _query = query; 157 | Reload(); 158 | } 159 | 160 | protected override TreeViewItem BuildRoot() 161 | { 162 | var root = new TreeViewItem { id = -1, depth = -1, displayName = "Root" }; 163 | var flxRoot = new FlxValueTreeViewItem(_rootValue, query:_query); 164 | root.AddChild(flxRoot); 165 | return root; 166 | } 167 | 168 | public FlexBufferTreeView(FlxValue rootValue, TreeViewState state) : base(state) 169 | { 170 | _rootValue = rootValue; 171 | Reload(); 172 | } 173 | 174 | 175 | } 176 | 177 | public class FlxValueTreeViewItem : TreeViewItem 178 | { 179 | public readonly FlxValue FlxValue; 180 | private int _depth; 181 | private FlxValueTreeViewItem _parent; 182 | private string _key; 183 | private List _children; 184 | private FlxQuery _query; 185 | 186 | public FlxValueTreeViewItem(FlxValue value, int depth = 0, FlxValueTreeViewItem parent = null, string key = "", FlxQuery query = null) 187 | { 188 | FlxValue = value; 189 | _depth = depth; 190 | _parent = parent; 191 | _key = key; 192 | _query = query; 193 | } 194 | 195 | public override int id => FlxValue.BufferOffset; 196 | public override string displayName { 197 | get 198 | { 199 | var type = FlxValue.ValueType; 200 | if (TypesUtil.IsAVector(type)) 201 | { 202 | return $"{_key}{type}[{FlxValue.AsVector.Length}]"; 203 | } 204 | 205 | if (type == Type.Map) 206 | { 207 | return $"{_key}{type}[{FlxValue.AsMap.Length}]"; 208 | } 209 | 210 | if (FlxValue.IsNull) 211 | { 212 | return $"{_key}null"; 213 | } 214 | 215 | if (type == Type.Bool) 216 | { 217 | return $"{_key}{FlxValue.AsBool}"; 218 | } 219 | 220 | if (type == Type.Blob) 221 | { 222 | return $"{_key}{FlxValue.ToJson}"; 223 | } 224 | 225 | if (type == Type.Float || type == Type.IndirectFloat) 226 | { 227 | return $"{_key}{FlxValue.AsDouble}"; 228 | } 229 | 230 | if (type == Type.Int || type == Type.IndirectInt) 231 | { 232 | return $"{_key}{FlxValue.AsLong}"; 233 | } 234 | 235 | if (type == Type.Uint || type == Type.IndirectUInt) 236 | { 237 | return $"{_key}{FlxValue.AsULong}"; 238 | } 239 | 240 | if (type == Type.String) 241 | { 242 | return $"{_key}'{FlxValue.AsString}'"; 243 | } 244 | 245 | return "UNKNOWN"; 246 | } 247 | } 248 | public override int depth => _depth; 249 | public override bool hasChildren => TypesUtil.IsAVector(FlxValue.ValueType) || FlxValue.ValueType == Type.Map; 250 | 251 | public override List children 252 | { 253 | get 254 | { 255 | if (TypesUtil.IsAVector(FlxValue.ValueType)) 256 | { 257 | var vec = FlxValue.AsVector; 258 | if (_children == null) 259 | { 260 | _children = new List(vec.Length); 261 | var index = 0; 262 | foreach (var item in vec) 263 | { 264 | if (_query != null) 265 | { 266 | var confirms = _query.Constraint.Confirms(vec, index); 267 | if (confirms) 268 | { 269 | _children.Add(new FlxValueTreeViewItem(item, _depth+1, this, $"{index} : ", _query.Propagating ? _query : _query.Next)); 270 | } else if (_query.Optional) 271 | { 272 | _children.Add(new FlxValueTreeViewItem(item, _depth+1, this, $"{index} : ", _query)); 273 | } 274 | } 275 | else 276 | { 277 | _children.Add(new FlxValueTreeViewItem(item, _depth+1, this, $"{index} : ")); 278 | } 279 | 280 | index++; 281 | } 282 | } 283 | 284 | return _children; 285 | } 286 | 287 | if (FlxValue.ValueType == Type.Map) 288 | { 289 | var map = FlxValue.AsMap; 290 | if (_children == null) 291 | { 292 | _children = new List(map.Length); 293 | foreach (var item in map) 294 | { 295 | if (_query != null) 296 | { 297 | var confirms = _query.Constraint.Confirms(map, item.Key); 298 | if (confirms) 299 | { 300 | _children.Add(new FlxValueTreeViewItem(item.Value, _depth+1, this, $"{item.Key} : ", _query.Next)); 301 | } else if (_query.Optional) 302 | { 303 | _children.Add(new FlxValueTreeViewItem(item.Value, _depth+1, this, $"{item.Key} : ", _query)); 304 | } 305 | } 306 | else 307 | { 308 | _children.Add(new FlxValueTreeViewItem(item.Value, _depth+1, this, $"{item.Key} : ")); 309 | } 310 | } 311 | } 312 | 313 | return _children; 314 | } 315 | 316 | return new List(); 317 | } 318 | } 319 | 320 | public override TreeViewItem parent => _parent; 321 | } -------------------------------------------------------------------------------- /Editor/FlexBufferBrowser.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 393635c570cac4e1699904de61d1fae3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/FlexBuffersPreferences.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | 3 | namespace FlexBuffers 4 | { 5 | public static class FlexBuffersPreferences 6 | { 7 | public static bool PrettyPrintedJson 8 | { 9 | get => EditorPrefs.GetBool("flex_buffer_pretty_printing", true); 10 | set => EditorPrefs.SetBool("flex_buffer_pretty_printing", value); 11 | } 12 | 13 | private static string _CsvSeparator 14 | { 15 | get => EditorPrefs.GetString("flex_buffer_csv_separator", ","); 16 | set => EditorPrefs.SetString("flex_buffer_csv_separator", value); 17 | } 18 | 19 | public static char CsvSeparator => _CsvSeparator.Length == 1 ? _CsvSeparator[0] : ','; 20 | 21 | [PreferenceItem("FlexBuffers")] 22 | private static void PreferencesItem() 23 | { 24 | 25 | PrettyPrintedJson = EditorGUILayout.Toggle("Export JSON pretty printed", PrettyPrintedJson); 26 | var separatorError = _CsvSeparator.Length != 1 ? "✘ ︎" : ""; 27 | _CsvSeparator = EditorGUILayout.TextField($"{separatorError}CSV separator", _CsvSeparator); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Editor/FlexBuffersPreferences.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d3bd8137bb5e4a0ab460b9bfd99335da 3 | timeCreated: 1575121624 -------------------------------------------------------------------------------- /Editor/FlxQuery.cs: -------------------------------------------------------------------------------- 1 | using FlexBuffers; 2 | 3 | namespace FlexBuffers 4 | { 5 | public class FlxQuery 6 | { 7 | public readonly bool Optional; 8 | public readonly bool Propagating; 9 | public readonly IConstraint Constraint; 10 | public FlxQuery Next = null; 11 | 12 | public FlxQuery(bool optional, IConstraint constraint, bool propagating) 13 | { 14 | Optional = optional; 15 | Constraint = constraint; 16 | Propagating = propagating; 17 | } 18 | } 19 | 20 | public interface IConstraint 21 | { 22 | bool Confirms(FlxMap value, string key); 23 | bool Confirms(FlxVector value, int index); 24 | } 25 | 26 | public class IsProperty : IConstraint 27 | { 28 | private readonly string _propertyName; 29 | 30 | public IsProperty(string propertyName) 31 | { 32 | _propertyName = propertyName; 33 | } 34 | 35 | public bool Confirms(FlxMap map, string key) 36 | { 37 | return key == _propertyName; 38 | } 39 | 40 | public bool Confirms(FlxVector value, int index) 41 | { 42 | return false; 43 | } 44 | } 45 | 46 | public class IsInIndexRangeConstraint : IConstraint 47 | { 48 | private readonly int? _start; 49 | private readonly int? _end; 50 | 51 | public IsInIndexRangeConstraint(int? start, int? end) 52 | { 53 | _start = start; 54 | _end = end; 55 | } 56 | 57 | public bool Confirms(FlxMap map, string key) 58 | { 59 | return false; 60 | } 61 | 62 | public bool Confirms(FlxVector value, int index) 63 | { 64 | var start = _start >= 0 || _start == null ? _start : value.Length + _start; 65 | var end = _end >= 0 || _end == null ? _end : value.Length + _end; 66 | if (index < value.Length) 67 | { 68 | if (start != null && start.Value > index) 69 | { 70 | return false; 71 | } 72 | 73 | return end == null || end.Value >= index; 74 | } 75 | 76 | return false; 77 | } 78 | } 79 | 80 | public class IsNumberConstraint : IConstraint 81 | { 82 | private readonly double? _min; 83 | private readonly double? _max; 84 | 85 | public IsNumberConstraint(double? min, double? max) 86 | { 87 | _min = min; 88 | _max = max; 89 | } 90 | 91 | public bool Confirms(FlxMap value, string key) 92 | { 93 | var flxValue = value[key]; 94 | if (IsNumber(flxValue) == false) 95 | { 96 | return true; 97 | } 98 | 99 | var number = flxValue.AsDouble; 100 | return IsInRange(number); 101 | } 102 | 103 | public bool Confirms(FlxVector value, int index) 104 | { 105 | var flxValue = value[index]; 106 | if (IsNumber(flxValue) == false) 107 | { 108 | return true; 109 | } 110 | 111 | var number = flxValue.AsDouble; 112 | return IsInRange(number); 113 | } 114 | 115 | bool IsNumber(FlxValue value) 116 | { 117 | return value.ValueType == Type.Int 118 | || value.ValueType == Type.Uint 119 | || value.ValueType == Type.Float 120 | || value.ValueType == Type.IndirectInt 121 | || value.ValueType == Type.IndirectUInt 122 | || value.ValueType == Type.IndirectFloat 123 | ; 124 | } 125 | 126 | bool IsInRange(double value) 127 | { 128 | if (_min != null && _min.Value > value) 129 | { 130 | return false; 131 | } 132 | 133 | return _max == null || _max.Value >= value; 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /Editor/FlxQuery.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3a3f81aa54e0b47e2b6a8093558e7baa 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/FlxQueryParser.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using JetBrains.Annotations; 3 | using FlexBuffers; 4 | 5 | namespace FlexBuffers 6 | { 7 | 8 | public static class FlxQueryParser 9 | { 10 | public static FlxQuery Convert(string query) 11 | { 12 | var cursor = 0; 13 | FlxQuery result = null; 14 | FlxQuery current = null; 15 | while (cursor < query.Length) 16 | { 17 | EatWhiteSpace(query, ref cursor); 18 | var propertyQuery = ParsePropertyQuery(query, ref cursor); 19 | if (propertyQuery != null) 20 | { 21 | if (current == null) 22 | { 23 | result = propertyQuery; 24 | current = propertyQuery; 25 | } 26 | else 27 | { 28 | current.Next = propertyQuery; 29 | current = propertyQuery; 30 | } 31 | continue; 32 | } 33 | 34 | var indexQuery = ParseIndexQuery(query, ref cursor); 35 | if (indexQuery != null) 36 | { 37 | if (current == null) 38 | { 39 | result = indexQuery; 40 | current = indexQuery; 41 | } 42 | else 43 | { 44 | current.Next = indexQuery; 45 | current = indexQuery; 46 | } 47 | continue; 48 | } 49 | 50 | var numberQuery = ParseNumberQuery(query, ref cursor); 51 | if (numberQuery != null) 52 | { 53 | if (current == null) 54 | { 55 | result = numberQuery; 56 | current = numberQuery; 57 | } 58 | else 59 | { 60 | current.Next = numberQuery; 61 | current = numberQuery; 62 | } 63 | continue; 64 | } 65 | 66 | break; 67 | } 68 | 69 | return result; 70 | } 71 | 72 | private static FlxQuery ParsePropertyQuery(string query, ref int cursor) 73 | { 74 | var initCursor = cursor; 75 | var optional = true; 76 | if (TryEat("..", query, ref cursor)) 77 | { 78 | } else if(TryEat(".", query, ref cursor)) 79 | { 80 | optional = false; 81 | } else 82 | { 83 | return null; 84 | } 85 | var propertyName = EatId(query, ref cursor); 86 | if (propertyName.Length == 0) 87 | { 88 | cursor = initCursor; 89 | return null; 90 | } 91 | return new FlxQuery(optional, new IsProperty(propertyName), false); 92 | } 93 | 94 | private static FlxQuery ParseIndexQuery(string query, ref int cursor) 95 | { 96 | var initCursor = cursor; 97 | var optional = true; 98 | if (TryEat("..", query, ref cursor)) 99 | { 100 | } else if(TryEat(".", query, ref cursor)) 101 | { 102 | optional = false; 103 | } else 104 | { 105 | return null; 106 | } 107 | 108 | if (TryEat("[", query, ref cursor) == false) 109 | { 110 | cursor = initCursor; 111 | return null; 112 | } 113 | 114 | var start = EatInt(query, ref cursor); 115 | var end = start; 116 | 117 | if (TryEat(":", query, ref cursor)) 118 | { 119 | end = EatInt(query, ref cursor); 120 | } 121 | 122 | if (TryEat("]", query, ref cursor) == false) 123 | { 124 | cursor = initCursor; 125 | return null; 126 | } 127 | 128 | return new FlxQuery(optional, new IsInIndexRangeConstraint(start, end), false); 129 | } 130 | 131 | private static FlxQuery ParseNumberQuery(string query, ref int cursor) 132 | { 133 | var initCursor = cursor; 134 | var optional = false; 135 | if (TryEat("..", query, ref cursor)) 136 | { 137 | } else if(TryEat(".", query, ref cursor)) 138 | { 139 | optional = false; 140 | } else 141 | { 142 | return null; 143 | } 144 | 145 | if (TryEat("{", query, ref cursor) == false) 146 | { 147 | cursor = initCursor; 148 | return null; 149 | } 150 | 151 | var start = EatDouble(query, ref cursor); 152 | var end = start; 153 | 154 | if (TryEat(":", query, ref cursor)) 155 | { 156 | end = EatDouble(query, ref cursor); 157 | } 158 | 159 | if (TryEat("}", query, ref cursor) == false) 160 | { 161 | cursor = initCursor; 162 | return null; 163 | } 164 | 165 | return new FlxQuery(optional, new IsNumberConstraint(start, end), true); 166 | } 167 | 168 | private static void EatWhiteSpace(string query, ref int cursor) 169 | { 170 | for (; cursor < query.Length; cursor++) 171 | { 172 | if (char.IsWhiteSpace(query, cursor) == false) 173 | { 174 | break; 175 | } 176 | } 177 | } 178 | 179 | private static string EatId(string query, ref int cursor) 180 | { 181 | var builder = new StringBuilder(); 182 | for (; cursor < query.Length; cursor++) 183 | { 184 | if (char.IsLetterOrDigit(query[cursor])) 185 | { 186 | builder.Append(query[cursor]); 187 | } 188 | else 189 | { 190 | break; 191 | } 192 | } 193 | 194 | return builder.ToString(); 195 | } 196 | 197 | private static int? EatInt(string query, ref int cursor) 198 | { 199 | EatWhiteSpace(query, ref cursor); 200 | var negative = TryEat("-", query, ref cursor); 201 | EatWhiteSpace(query, ref cursor); 202 | var builder = new StringBuilder(); 203 | for (; cursor < query.Length; cursor++) 204 | { 205 | if (char.IsDigit(query[cursor])) 206 | { 207 | builder.Append(query[cursor]); 208 | } 209 | else 210 | { 211 | break; 212 | } 213 | } 214 | 215 | if (builder.Length == 0) 216 | { 217 | return null; 218 | } 219 | 220 | var number = int.Parse(builder.ToString()); 221 | return negative ? - number : number ; 222 | } 223 | 224 | private static double? EatDouble(string query, ref int cursor) 225 | { 226 | EatWhiteSpace(query, ref cursor); 227 | var negative = TryEat("-", query, ref cursor); 228 | EatWhiteSpace(query, ref cursor); 229 | var builder = new StringBuilder(); 230 | for (; cursor < query.Length; cursor++) 231 | { 232 | if (char.IsDigit(query[cursor]) || query[cursor] == '.') 233 | { 234 | builder.Append(query[cursor]); 235 | } 236 | else 237 | { 238 | break; 239 | } 240 | } 241 | 242 | if (builder.Length == 0) 243 | { 244 | return null; 245 | } 246 | 247 | var number = double.Parse(builder.ToString()); 248 | return negative ? - number : number ; 249 | } 250 | 251 | private static bool TryEat(string value, string query, ref int cursor) 252 | { 253 | var initCursor = cursor; 254 | var length = value.Length; 255 | if (query.Length < cursor + length) 256 | { 257 | return false; 258 | } 259 | 260 | foreach (var c1 in value) 261 | { 262 | if (query[cursor] == c1) 263 | { 264 | cursor++; 265 | } 266 | else 267 | { 268 | cursor = initCursor; 269 | return false; 270 | } 271 | } 272 | 273 | return true; 274 | } 275 | 276 | [CanBeNull] 277 | private static string EatUntil(string value, string query, ref int cursor) 278 | { 279 | var initCursor = cursor; 280 | var outerBuilder = new StringBuilder(); 281 | for (; cursor < query.Length;) 282 | { 283 | if (query[cursor] == value[0]) 284 | { 285 | var builder = new StringBuilder(); 286 | for (var i = 0; i < value.Length; i++) 287 | { 288 | if (query[cursor] == value[i]) 289 | { 290 | builder.Append(query[cursor]); 291 | cursor++; 292 | } 293 | else 294 | { 295 | outerBuilder.Append(builder.ToString()); 296 | break; 297 | } 298 | 299 | if (cursor >= query.Length) 300 | { 301 | cursor = initCursor; 302 | return null; 303 | } 304 | } 305 | 306 | return outerBuilder.ToString(); 307 | } 308 | else 309 | { 310 | outerBuilder.Append(query[cursor]); 311 | } 312 | 313 | cursor++; 314 | } 315 | 316 | cursor = initCursor; 317 | return null; 318 | } 319 | } 320 | } -------------------------------------------------------------------------------- /Editor/FlxQueryParser.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c631449d6f91942dda9b3f5fb57c2188 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ImportAsFlexBuffer.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using FlexBuffers; 3 | using UnityEditor; 4 | 5 | public static class ImportAsFlexBuffer 6 | { 7 | [MenuItem("Tools/FlexBuffers/JSON as FlexBuffer...")] 8 | static void ImportJson() 9 | { 10 | string jsonPath = EditorUtility.OpenFilePanel("Select JSON file", "", "json"); 11 | if (jsonPath.Length == 0) 12 | { 13 | return; 14 | } 15 | var bytes = JsonToFlexBufferConverter.ConvertFile(jsonPath); 16 | 17 | if (bytes == null) 18 | { 19 | return; 20 | } 21 | 22 | var fileName = Path.GetFileNameWithoutExtension(jsonPath); 23 | 24 | var flxPath = EditorUtility.SaveFilePanel( 25 | "Save as FlexBuffer", 26 | "", 27 | fileName + ".bytes", 28 | "bytes"); 29 | 30 | if (flxPath.Length != 0) 31 | { 32 | File.WriteAllBytes(flxPath, bytes); 33 | } 34 | } 35 | 36 | [MenuItem("Tools/FlexBuffers/CSV as FlexBuffer...")] 37 | static void ImportCsv() 38 | { 39 | string csvPath = EditorUtility.OpenFilePanel("Select CSV file", "", "csv"); 40 | if (csvPath.Length == 0) 41 | { 42 | return; 43 | } 44 | 45 | var csv = File.ReadAllText(csvPath); 46 | var bytes = CsvToFlexBufferConverter.Convert(csv, FlexBuffersPreferences.CsvSeparator); 47 | 48 | if (bytes == null) 49 | { 50 | return; 51 | } 52 | 53 | var fileName = Path.GetFileNameWithoutExtension(csvPath); 54 | 55 | var flxPath = EditorUtility.SaveFilePanel( 56 | "Save as FlexBuffer", 57 | "", 58 | fileName + ".bytes", 59 | "bytes"); 60 | 61 | if (flxPath.Length != 0) 62 | { 63 | File.WriteAllBytes(flxPath, bytes); 64 | } 65 | } 66 | 67 | [MenuItem("Tools/FlexBuffers/XML as FlexBuffer...")] 68 | static void ImportXML() 69 | { 70 | string xmlPath = EditorUtility.OpenFilePanel("Select XML file", "", "xml"); 71 | if (xmlPath.Length == 0) 72 | { 73 | return; 74 | } 75 | 76 | var xmlData = File.ReadAllText(xmlPath); 77 | var bytes = XmlToFlexBufferConverter.Convert(xmlData); 78 | 79 | if (bytes == null) 80 | { 81 | return; 82 | } 83 | 84 | var fileName = Path.GetFileNameWithoutExtension(xmlPath); 85 | 86 | var flxPath = EditorUtility.SaveFilePanel( 87 | "Save as FlexBuffer", 88 | "", 89 | fileName + ".bytes", 90 | "bytes"); 91 | 92 | if (flxPath.Length != 0) 93 | { 94 | File.WriteAllBytes(flxPath, bytes); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Editor/ImportAsFlexBuffer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 44c6b2a1545eb48fbae00e997779270b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Unity.FlexBuffers.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "FlexBuffersEditor", 3 | "references": [ 4 | "GUID:1a82e366e6a014b228ca1f953480ed08" 5 | ], 6 | "includePlatforms": [ 7 | "Editor" 8 | ], 9 | "excludePlatforms": [], 10 | "allowUnsafeCode": false, 11 | "overrideReferences": false, 12 | "precompiledReferences": [], 13 | "autoReferenced": true, 14 | "defineConstraints": [], 15 | "versionDefines": [], 16 | "noEngineReferences": false 17 | } -------------------------------------------------------------------------------- /Editor/Unity.FlexBuffers.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1940baab2af5c4cd492c36bc9a494556 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Maxim Zaks 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. 22 | -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7e2a358f63b85470796d6b016bdc8fb4 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FlexBuffersUnity 2 | Unity package bringing FlexBuffers support to your Unity3D projects 3 | 4 | Please read this blog post for more details: 5 | https://medium.com/@icex33/flexbuffers-for-unity3d-4d1ab5c53fbe 6 | 7 | Also have a look at the parent repository FlexBuffers-CSharp for API details: 8 | https://github.com/mzaks/FlexBuffers-CSharp 9 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 088f8d6caf0ce46029ea03305791abd2 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ae3c452e571314db7b3953eafb9fcf62 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/BitWidth.cs: -------------------------------------------------------------------------------- 1 | namespace FlexBuffers 2 | { 3 | public enum BitWidth: byte 4 | { 5 | Width8, Width16, Width32, Width64 6 | } 7 | 8 | public static class BitWidthUtil 9 | { 10 | public static BitWidth Width(sbyte value) 11 | { 12 | return BitWidth.Width8; 13 | } 14 | 15 | public static BitWidth Width(short value) 16 | { 17 | if (value >= 0) 18 | { 19 | return value <= sbyte.MaxValue ? BitWidth.Width8 : BitWidth.Width16; 20 | } 21 | return value >= sbyte.MinValue ? BitWidth.Width8 : BitWidth.Width16; 22 | } 23 | 24 | public static BitWidth Width(int value) 25 | { 26 | if (value >= 0) 27 | { 28 | if (value <= sbyte.MaxValue) 29 | { 30 | return BitWidth.Width8; 31 | } 32 | return value <= short.MaxValue ? BitWidth.Width16 : BitWidth.Width32; 33 | } 34 | if (value >= sbyte.MinValue) 35 | { 36 | return BitWidth.Width8; 37 | } 38 | return value >= short.MinValue ? BitWidth.Width16 : BitWidth.Width32; 39 | } 40 | 41 | public static BitWidth Width(long value) 42 | { 43 | if (value >= 0) 44 | { 45 | return value <= int.MaxValue ? Width((int) value) : BitWidth.Width64; 46 | } 47 | else 48 | { 49 | return value >= int.MinValue ? Width((int) value) : BitWidth.Width64; 50 | } 51 | } 52 | 53 | public static BitWidth Width(byte value) 54 | { 55 | return BitWidth.Width8; 56 | } 57 | 58 | public static BitWidth Width(ushort value) 59 | { 60 | return value <= byte.MaxValue ? BitWidth.Width8 : BitWidth.Width16; 61 | } 62 | 63 | public static BitWidth Width(uint value) 64 | { 65 | if (value <= byte.MaxValue) 66 | { 67 | return BitWidth.Width8; 68 | } 69 | 70 | return value <= ushort.MaxValue ? BitWidth.Width16 : BitWidth.Width32; 71 | } 72 | 73 | public static BitWidth Width(ulong value) 74 | { 75 | return value <= uint.MaxValue ? Width((uint) value) : BitWidth.Width64; 76 | } 77 | 78 | public static BitWidth Width(float value) 79 | { 80 | return BitWidth.Width32; 81 | } 82 | 83 | public static BitWidth Width(double value) 84 | { 85 | return ((double)((float)value)) == value ? BitWidth.Width32 : BitWidth.Width64; 86 | } 87 | 88 | public static ulong PaddingSize(ulong bufSize, ulong scalarSize) 89 | { 90 | return (~bufSize + 1) & (scalarSize - 1); 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /Runtime/BitWidth.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 55cc9c4ca9dca4a78846736f76444890 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/CsvToFlexBufferConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace FlexBuffers 4 | { 5 | public static class CsvToFlexBufferConverter 6 | { 7 | public static byte[] Convert(string csv, char separator = ',') 8 | { 9 | var flx = new FlexBuffer(); 10 | var sb = new StringBuilder(); 11 | var outerVec = flx.StartVector(); 12 | var innerVec = -1; 13 | var isInDoubleQuotes = false; 14 | for (var offset = 0; offset < csv.Length;) 15 | { 16 | if (innerVec == -1) 17 | { 18 | innerVec = flx.StartVector(); 19 | } 20 | 21 | if (csv[offset] == '"') 22 | { 23 | if (isInDoubleQuotes == false) 24 | { 25 | isInDoubleQuotes = true; 26 | offset++; 27 | } 28 | else 29 | { 30 | if (csv.Length > offset + 1 && csv[offset + 1] == '"') 31 | { 32 | sb.Append('"'); 33 | offset += 2; 34 | } 35 | else 36 | { 37 | isInDoubleQuotes = false; 38 | offset++; 39 | } 40 | } 41 | 42 | } else if (csv[offset] == separator && isInDoubleQuotes == false) 43 | { 44 | flx.Add(sb.ToString()); 45 | sb.Clear(); 46 | offset++; 47 | } else if (csv[offset] == '\n' && isInDoubleQuotes == false) 48 | { 49 | flx.Add(sb.ToString()); 50 | flx.EndVector(innerVec, false, false); 51 | innerVec = -1; 52 | sb.Clear(); 53 | offset++; 54 | } else if (csv[offset] == '\r' && csv.Length > offset + 1 && csv[offset+1] == '\n' && isInDoubleQuotes == false) 55 | { 56 | flx.Add(sb.ToString()); 57 | flx.EndVector(innerVec, false, false); 58 | innerVec = -1; 59 | sb.Clear(); 60 | offset += 2; 61 | } 62 | else 63 | { 64 | sb.Append(csv[offset]); 65 | offset++; 66 | } 67 | } 68 | 69 | if (innerVec != -1) 70 | { 71 | flx.Add(sb.ToString()); 72 | flx.EndVector(innerVec, false, false); 73 | } 74 | flx.EndVector(outerVec, false, false); 75 | return flx.Finish(); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /Runtime/CsvToFlexBufferConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d49251ffe3df948ce9f7118e64a47aba 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/FlexBuffer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace FlexBuffers 7 | { 8 | public class FlexBuffer 9 | { 10 | [Flags] 11 | public enum Options: byte 12 | { 13 | None = 0, 14 | ShareKeys = 1, 15 | ShareStrings = 1 << 1, 16 | ShareKeyVectors = 1 << 2, 17 | } 18 | private readonly List _stack = new List(); 19 | private readonly Dictionary _stringCache = new Dictionary(); 20 | private readonly Dictionary _keyCache = new Dictionary(); 21 | private readonly Dictionary _keyVectorCache = new Dictionary(new OffsetArrayComparer()); 22 | private byte[] _bytes; 23 | private ulong _size = 2048; 24 | private ulong _offset; 25 | private readonly Options _options; 26 | private bool _finished = false; 27 | 28 | public FlexBuffer(ulong size = 2048, Options options = Options.ShareKeys | Options.ShareStrings | Options.ShareKeyVectors) 29 | { 30 | if (size > 0) 31 | { 32 | _size = size; 33 | } 34 | _bytes = new byte[size]; 35 | _offset = 0; 36 | _options = options; 37 | } 38 | 39 | public static byte[] Null() 40 | { 41 | var buffer = new FlexBuffer(3); 42 | buffer.AddNull(); 43 | return buffer.Finish(); 44 | } 45 | 46 | public static byte[] SingleValue(long value) 47 | { 48 | var buffer = new FlexBuffer(10); 49 | buffer.Add(value); 50 | return buffer.Finish(); 51 | } 52 | 53 | public static byte[] SingleValue(ulong value) 54 | { 55 | var buffer = new FlexBuffer(10); 56 | buffer.Add(value); 57 | return buffer.Finish(); 58 | } 59 | 60 | public static byte[] SingleValue(double value) 61 | { 62 | var buffer = new FlexBuffer(10); 63 | buffer.Add(value); 64 | return buffer.Finish(); 65 | } 66 | 67 | public static byte[] SingleValue(bool value) 68 | { 69 | var buffer = new FlexBuffer(3); 70 | buffer.Add(value); 71 | return buffer.Finish(); 72 | } 73 | 74 | public static byte[] SingleValue(string value) 75 | { 76 | var buffer = new FlexBuffer((ulong)value.Length + 2); 77 | buffer.Add(value); 78 | return buffer.Finish(); 79 | } 80 | 81 | public static byte[] SingleValue(long x, long y) 82 | { 83 | var buffer = new FlexBuffer(20); 84 | var start = buffer.StartVector(); 85 | buffer.Add(x); 86 | buffer.Add(y); 87 | buffer.EndVector(start, true, true); 88 | return buffer.Finish(); 89 | } 90 | 91 | public static byte[] SingleValue(long x, long y, long z) 92 | { 93 | var buffer = new FlexBuffer(28); 94 | var start = buffer.StartVector(); 95 | buffer.Add(x); 96 | buffer.Add(y); 97 | buffer.Add(z); 98 | buffer.EndVector(start, true, true); 99 | return buffer.Finish(); 100 | } 101 | 102 | public static byte[] SingleValue(long x, long y, long z, long w) 103 | { 104 | var buffer = new FlexBuffer(36); 105 | var start = buffer.StartVector(); 106 | buffer.Add(x); 107 | buffer.Add(y); 108 | buffer.Add(z); 109 | buffer.Add(w); 110 | buffer.EndVector(start, true, true); 111 | return buffer.Finish(); 112 | } 113 | 114 | public static byte[] SingleValue(ulong x, ulong y) 115 | { 116 | var buffer = new FlexBuffer(20); 117 | var start = buffer.StartVector(); 118 | buffer.Add(x); 119 | buffer.Add(y); 120 | buffer.EndVector(start, true, true); 121 | return buffer.Finish(); 122 | } 123 | 124 | public static byte[] SingleValue(ulong x, ulong y, ulong z) 125 | { 126 | var buffer = new FlexBuffer(28); 127 | var start = buffer.StartVector(); 128 | buffer.Add(x); 129 | buffer.Add(y); 130 | buffer.Add(z); 131 | buffer.EndVector(start, true, true); 132 | return buffer.Finish(); 133 | } 134 | 135 | public static byte[] SingleValue(ulong x, ulong y, ulong z, ulong w) 136 | { 137 | var buffer = new FlexBuffer(36); 138 | var start = buffer.StartVector(); 139 | buffer.Add(x); 140 | buffer.Add(y); 141 | buffer.Add(z); 142 | buffer.Add(w); 143 | buffer.EndVector(start, true, true); 144 | return buffer.Finish(); 145 | } 146 | 147 | public static byte[] SingleValue(double x, double y) 148 | { 149 | var buffer = new FlexBuffer(20); 150 | var start = buffer.StartVector(); 151 | buffer.Add(x); 152 | buffer.Add(y); 153 | buffer.EndVector(start, true, true); 154 | return buffer.Finish(); 155 | } 156 | 157 | public static byte[] SingleValue(double x, double y, double z) 158 | { 159 | var buffer = new FlexBuffer(28); 160 | var start = buffer.StartVector(); 161 | buffer.Add(x); 162 | buffer.Add(y); 163 | buffer.Add(z); 164 | buffer.EndVector(start, true, true); 165 | return buffer.Finish(); 166 | } 167 | 168 | public static byte[] SingleValue(double x, double y, double z, double w) 169 | { 170 | var buffer = new FlexBuffer(36); 171 | var start = buffer.StartVector(); 172 | buffer.Add(x); 173 | buffer.Add(y); 174 | buffer.Add(z); 175 | buffer.Add(w); 176 | buffer.EndVector(start, true, true); 177 | return buffer.Finish(); 178 | } 179 | 180 | public static byte[] SingleValue(byte[] blob) 181 | { 182 | var buffer = new FlexBuffer((ulong)blob.Length + 10); 183 | buffer.Add(blob); 184 | return buffer.Finish(); 185 | } 186 | 187 | public static byte[] From(IEnumerable value, Options options = Options.ShareKeys | Options.ShareStrings | Options.ShareKeyVectors) 188 | { 189 | var buffer = new FlexBuffer(options:options); 190 | if (value is IDictionary dictionary) 191 | { 192 | buffer.AddDynamicMap(dictionary); 193 | } 194 | else 195 | { 196 | buffer.AddDynamicVector(value); 197 | } 198 | 199 | return buffer.Finish(); 200 | } 201 | 202 | public byte[] Finish() 203 | { 204 | if (_finished == false) 205 | { 206 | FinishBuffer(); 207 | } 208 | var result = new byte[_offset]; 209 | Buffer.BlockCopy(_bytes, 0, result, 0, (int) _offset); 210 | return result; 211 | } 212 | 213 | private void FinishBuffer() 214 | { 215 | if (_finished) 216 | { 217 | throw new Exception("FlexBuffer is already finished"); 218 | } 219 | 220 | if (_stack.Count != 1) 221 | { 222 | throw new Exception("Stack needs to be exactly 1"); 223 | } 224 | 225 | var value = _stack[0]; 226 | 227 | var byteWidth = Align(value.ElementWidth(_offset, 0)); 228 | 229 | Write(value, byteWidth); 230 | Write(value.StoredPackedType()); 231 | Write(byteWidth); 232 | _finished = true; 233 | } 234 | 235 | internal Type AddNull() 236 | { 237 | _stack.Add(StackValue.Null()); 238 | return Type.Null; 239 | } 240 | 241 | internal Type Add(long value) 242 | { 243 | _stack.Add(StackValue.Value(value)); 244 | return Type.Int; 245 | } 246 | 247 | internal Type AddIndirect(long value) 248 | { 249 | var type = Type.IndirectInt; 250 | var bitWidth = BitWidthUtil.Width(value); 251 | var byteWidth = Align(bitWidth); 252 | var valueOffset = _offset; 253 | Write(value, byteWidth); 254 | _stack.Add(StackValue.Value(valueOffset, bitWidth, type)); 255 | return type; 256 | } 257 | 258 | internal Type Add(ulong value) 259 | { 260 | _stack.Add(StackValue.Value(value)); 261 | return Type.Uint; 262 | } 263 | 264 | internal Type AddIndirect(ulong value) 265 | { 266 | var type = Type.IndirectUInt; 267 | var bitWidth = BitWidthUtil.Width(value); 268 | var byteWidth = Align(bitWidth); 269 | var valueOffset = _offset; 270 | Write(value, byteWidth); 271 | _stack.Add(StackValue.Value(valueOffset, bitWidth, type)); 272 | return type; 273 | } 274 | 275 | internal Type Add(double value) 276 | { 277 | _stack.Add(StackValue.Value(value)); 278 | return Type.Float; 279 | } 280 | 281 | internal Type AddIndirect(double value) 282 | { 283 | var type = Type.IndirectFloat; 284 | var bitWidth = BitWidthUtil.Width(value); 285 | var byteWidth = Align(bitWidth); 286 | var valueOffset = _offset; 287 | Write(value, byteWidth); 288 | _stack.Add(StackValue.Value(valueOffset, bitWidth, type)); 289 | return type; 290 | } 291 | 292 | internal Type Add(bool value) 293 | { 294 | _stack.Add(StackValue.Value(value)); 295 | return Type.Bool; 296 | } 297 | 298 | internal Type Add(string value) 299 | { 300 | 301 | var bytes = Encoding.UTF8.GetBytes(value); 302 | var length = (ulong)bytes.Length; 303 | var bitWidth = BitWidthUtil.Width(length); 304 | if (_options.HasFlag(Options.ShareStrings) && _stringCache.ContainsKey(value)) 305 | { 306 | _stack.Add(StackValue.Value(_stringCache[value], bitWidth, Type.String)); 307 | return Type.String; 308 | } 309 | var byteWidth = Align(bitWidth); 310 | Write(length, byteWidth); 311 | var stringOffset = _offset; 312 | var newOffset = NewOffset(length + 1); 313 | Buffer.BlockCopy(bytes, 0, _bytes, (int)_offset, (int)length); 314 | _offset = newOffset; 315 | _stack.Add(StackValue.Value(stringOffset, bitWidth, Type.String)); 316 | if (_options.HasFlag(Options.ShareStrings)) 317 | { 318 | _stringCache[value] = stringOffset; 319 | } 320 | return Type.String; 321 | } 322 | 323 | internal Type Add(byte[] value) 324 | { 325 | var length = (ulong)value.Length; 326 | var bitWidth = BitWidthUtil.Width(length); 327 | var byteWidth = Align(bitWidth); 328 | Write(value.Length, byteWidth); 329 | 330 | var newOffset = NewOffset(length); 331 | var blobOffset = _offset; 332 | Buffer.BlockCopy(value, 0, _bytes, (int)_offset, value.Length); 333 | _offset = newOffset; 334 | _stack.Add(StackValue.Value(blobOffset, bitWidth, Type.Blob)); 335 | return Type.Blob; 336 | } 337 | 338 | private void AddDynamicVector(IEnumerable values) 339 | { 340 | var start = StartVector(); 341 | var typed = true; 342 | var prevType = -1; 343 | foreach (object value in values) 344 | { 345 | var currentType = AddDynamic(value); 346 | 347 | if (typed == false || TypesUtil.IsTypedVectorElement(currentType) == false) 348 | { 349 | typed = false; 350 | continue; 351 | } 352 | 353 | if (prevType == -1) 354 | { 355 | prevType = (int)currentType; 356 | } 357 | 358 | if (typed) 359 | { 360 | typed = prevType == (int)currentType; 361 | } 362 | } 363 | EndVector(start, typed, false); 364 | } 365 | 366 | private void AddDynamicMap(IDictionary values) 367 | { 368 | var start = StartVector(); 369 | var keyStrings = new List(values.Count); 370 | foreach (var key in values.Keys) 371 | { 372 | if (key is string s) 373 | { 374 | keyStrings.Add(s); 375 | } 376 | else 377 | { 378 | throw new Exception($"Key {key} is not a string."); 379 | } 380 | } 381 | 382 | foreach (var key in keyStrings) 383 | { 384 | AddKey(key); 385 | AddDynamic(values[key]); 386 | } 387 | 388 | SortAndEndMap(start); 389 | } 390 | 391 | internal void SortAndEndMap(int start) 392 | { 393 | if (((_stack.Count - start) & 1) == 1) 394 | { 395 | throw new Exception("The stack needs to hold key value pairs (even number of elements)"); 396 | } 397 | 398 | var sorted = true; 399 | for (var i = start; i < _stack.Count - 2; i+=2) 400 | { 401 | if (ShouldFlip(_stack[i], _stack[i + 2])) 402 | { 403 | sorted = false; 404 | break; 405 | } 406 | } 407 | 408 | if (sorted == false) 409 | { 410 | for (var i = start; i < _stack.Count; i += 2) 411 | { 412 | var flipIndex = i; 413 | for (var j = i + 2; j < _stack.Count; j += 2) 414 | { 415 | if (ShouldFlip(_stack[flipIndex], _stack[j])) 416 | { 417 | flipIndex = j; 418 | } 419 | } 420 | 421 | if (flipIndex != i) 422 | { 423 | var k = _stack[flipIndex]; 424 | var v = _stack[flipIndex + 1]; 425 | _stack[flipIndex] = _stack[i]; 426 | _stack[flipIndex + 1] = _stack[i + 1]; 427 | _stack[i] = k; 428 | _stack[i + 1] = v; 429 | 430 | } 431 | } 432 | } 433 | 434 | EndMap(start); 435 | } 436 | 437 | private void EndMap(int start) 438 | { 439 | var vecLen = (_stack.Count - start) / 2; 440 | StackValue keys; 441 | if (_options.HasFlag(Options.ShareKeyVectors)) 442 | { 443 | var offsets = new long[vecLen]; 444 | for (var i = start; i < _stack.Count; i += 2) 445 | { 446 | offsets[(i - start) / 2] = _stack[i].AsLong; 447 | } 448 | 449 | if (_keyVectorCache.ContainsKey(offsets)) 450 | { 451 | keys = _keyVectorCache[offsets]; 452 | } 453 | else 454 | { 455 | keys = CreateVector(start, vecLen, 2, true, false); 456 | _keyVectorCache[offsets] = keys; 457 | } 458 | } 459 | else 460 | { 461 | keys = CreateVector(start, vecLen, 2, true, false); 462 | } 463 | 464 | var vec = CreateVector(start + 1, vecLen, 2, false, false, keys); 465 | _stack.RemoveRange(_stack.Count - vecLen * 2, vecLen * 2); 466 | _stack.Add(vec); 467 | } 468 | 469 | private bool ShouldFlip(StackValue v1, StackValue v2) 470 | { 471 | if (v1.TypeOfValue != Type.Key || v2.TypeOfValue != Type.Key) 472 | { 473 | throw new Exception($"Stack values are not keys {v1} | {v2}"); 474 | } 475 | 476 | byte c1, c2; 477 | var index = 0; 478 | do 479 | { 480 | c1 = _bytes[v1.AsLong + index]; 481 | c2 = _bytes[v2.AsLong + index]; 482 | if (c2 < c1) 483 | { 484 | return true; 485 | } 486 | 487 | if (c1 < c2) 488 | { 489 | return false; 490 | } 491 | 492 | index++; 493 | } while (c1 != 0 && c2 != 0); 494 | 495 | return false; 496 | } 497 | 498 | private Type AddDynamic(object value) 499 | { 500 | switch (value) 501 | { 502 | case null: 503 | return AddNull(); 504 | case string s1: 505 | return Add(s1); 506 | case bool b1: 507 | return Add(b1); 508 | case sbyte i1: 509 | return Add(i1); 510 | case short i1: 511 | return Add(i1); 512 | case int i1: 513 | return Add(i1); 514 | case long i1: 515 | return Add(i1); 516 | case byte l1: 517 | return Add(l1); 518 | case ushort l1: 519 | return Add(l1); 520 | case uint l1: 521 | return Add(l1); 522 | case ulong l1: 523 | return Add(l1); 524 | case double d1: 525 | return Add(d1); 526 | case IDictionary d: 527 | AddDynamicMap(d); 528 | return Type.Map; 529 | case IEnumerable l: 530 | AddDynamicVector(l); 531 | return Type.Vector; 532 | default: 533 | throw new Exception($"Unexpected type of {value}"); 534 | } 535 | } 536 | 537 | internal void AddKey(string value) 538 | { 539 | if (_options.HasFlag(Options.ShareKeys) && _keyCache.ContainsKey(value)) 540 | { 541 | _stack.Add(StackValue.Value(_keyCache[value], BitWidth.Width8, Type.Key)); 542 | return; 543 | } 544 | var bytes = Encoding.UTF8.GetBytes(value); 545 | var length = (ulong)bytes.Length; 546 | var keyOffset = _offset; 547 | var newOffset = NewOffset(length + 1); 548 | Buffer.BlockCopy(bytes, 0, _bytes, (int)_offset, (int)length); 549 | _offset = newOffset; 550 | _stack.Add(StackValue.Value(keyOffset, BitWidth.Width8, Type.Key)); 551 | if (_options.HasFlag(Options.ShareKeys)) 552 | { 553 | _keyCache[value] = keyOffset; 554 | } 555 | } 556 | 557 | private byte Align(BitWidth width) 558 | { 559 | var byteWidth = 1UL << (int) width; 560 | _offset += BitWidthUtil.PaddingSize(_offset, byteWidth); 561 | return (byte) byteWidth; 562 | } 563 | 564 | private void Write(StackValue value, ulong width) 565 | { 566 | var newOffset = NewOffset(width); 567 | if (value.IsOffset) 568 | { 569 | var relOffset = _offset - value.AsULong; 570 | if (width == 8 || relOffset < (ulong)1 << ((int)width * 8)) 571 | { 572 | Write(relOffset, width); 573 | } 574 | else 575 | { 576 | throw new Exception("Unexpected size"); 577 | } 578 | } 579 | else 580 | { 581 | var bytes = value.IsFloat32 && width == 4 ? BitConverter.GetBytes((float)value.AsDouble) : BitConverter.GetBytes(value.AsULong); 582 | var count = Math.Min((ulong)bytes.Length, width); 583 | Buffer.BlockCopy(bytes, 0, _bytes, (int)_offset, (int)count); 584 | } 585 | _offset = newOffset; 586 | } 587 | 588 | private void Write(byte value) 589 | { 590 | var newOffset = NewOffset(1); 591 | _bytes[_offset] = value; 592 | _offset = newOffset; 593 | } 594 | 595 | private void Write(long value, ulong width) 596 | { 597 | var newOffset = NewOffset(width); 598 | var bytes = BitConverter.GetBytes(value); 599 | var count = Math.Min((ulong)bytes.Length, width); 600 | Buffer.BlockCopy(bytes, 0, _bytes, (int)_offset, (int)count); 601 | _offset = newOffset; 602 | } 603 | 604 | private void Write(ulong value, ulong width) 605 | { 606 | var newOffset = NewOffset(width); 607 | var bytes = BitConverter.GetBytes(value); 608 | var count = Math.Min((ulong)bytes.Length, width); 609 | Buffer.BlockCopy(bytes, 0, _bytes, (int)_offset, (int)count); 610 | _offset = newOffset; 611 | } 612 | 613 | private void Write(double value, ulong width) 614 | { 615 | var newOffset = NewOffset(width); 616 | var bytes = BitConverter.GetBytes(value); 617 | var count = Math.Min((ulong)bytes.Length, width); 618 | Buffer.BlockCopy(bytes, 0, _bytes, (int)_offset, (int)count); 619 | _offset = newOffset; 620 | } 621 | 622 | private ulong NewOffset(ulong width) 623 | { 624 | var newOffset = _offset + width; 625 | var prevSize = _size; 626 | while (_size < newOffset) 627 | { 628 | _size <<= 1; 629 | } 630 | 631 | if (prevSize < _size) 632 | { 633 | var prevBytes = _bytes; 634 | _bytes = new byte[_size]; 635 | Buffer.BlockCopy(prevBytes, 0, _bytes, 0, (int)_offset); 636 | } 637 | 638 | return newOffset; 639 | } 640 | 641 | internal int StartVector() 642 | { 643 | return _stack.Count; 644 | } 645 | 646 | internal int EndVector(int start, bool typed, bool fix) 647 | { 648 | var vecLen = _stack.Count - start; 649 | var vec = CreateVector(start, vecLen, 1, typed, fix); 650 | 651 | _stack.RemoveRange(_stack.Count - vecLen, vecLen); 652 | _stack.Add(vec); 653 | return (int)vec.AsLong; 654 | } 655 | 656 | private StackValue CreateVector(int start, int vecLen, int step, bool typed, bool fix, StackValue? keys = null) 657 | { 658 | var bitWidth = BitWidthUtil.Width(vecLen); 659 | var prefixElems = 1; 660 | if (keys != null) 661 | { 662 | var elemWidth = keys.Value.ElementWidth(_offset, 0); 663 | if ((int) elemWidth > (int) bitWidth) 664 | { 665 | bitWidth = elemWidth; 666 | } 667 | 668 | prefixElems += 2; 669 | } 670 | 671 | var vectorType = Type.Key; 672 | for (var i = start; i < _stack.Count; i+=step) 673 | { 674 | var elemWidth = _stack[i].ElementWidth(_offset, i + prefixElems); 675 | if ((int) elemWidth > (int) bitWidth) 676 | { 677 | bitWidth = elemWidth; 678 | } 679 | 680 | if (typed) 681 | { 682 | if (i == start) 683 | { 684 | vectorType = _stack[i].TypeOfValue; 685 | } 686 | else 687 | { 688 | if (vectorType != _stack[i].TypeOfValue) 689 | { 690 | throw new Exception($"Your typed vector is of type {vectorType} but the item on index {i} is of type {_stack[i].TypeOfValue}"); 691 | } 692 | } 693 | } 694 | } 695 | 696 | if (TypesUtil.IsTypedVectorElement(vectorType) == false) 697 | { 698 | throw new Exception("Your fixed types are not one of: Int / UInt / Float / Key"); 699 | } 700 | 701 | var byteWidth = Align(bitWidth); 702 | if (keys != null) 703 | { 704 | Write(keys.Value, byteWidth); 705 | Write(1 << (int)keys.Value.InternalWidth, byteWidth); 706 | } 707 | 708 | if (!fix) 709 | { 710 | Write(vecLen, byteWidth); 711 | } 712 | 713 | var vloc = _offset; 714 | 715 | for (var i = start; i < _stack.Count; i += step) 716 | { 717 | Write(_stack[i], byteWidth); 718 | } 719 | 720 | if (!typed) 721 | { 722 | for (var i = start; i < _stack.Count; i += step) 723 | { 724 | Write(_stack[i].StoredPackedType()); 725 | } 726 | } 727 | 728 | 729 | if (keys != null) 730 | { 731 | return StackValue.Value(vloc, bitWidth, Type.Map); 732 | } 733 | 734 | if (typed) 735 | { 736 | var type = TypesUtil.ToTypedVector(vectorType, (byte)(fix ? vecLen : 0)); 737 | return StackValue.Value(vloc, bitWidth, type); 738 | } 739 | 740 | return StackValue.Value(vloc, bitWidth, Type.Vector); 741 | } 742 | } 743 | 744 | internal class OffsetArrayComparer : IEqualityComparer 745 | { 746 | public bool Equals(long[] x, long[] y) 747 | { 748 | if (x.Length != y.Length) 749 | { 750 | return false; 751 | } 752 | for (var i = 0; i < x.Length; i++) 753 | { 754 | if (x[i] != y[i]) 755 | { 756 | return false; 757 | } 758 | } 759 | return true; 760 | } 761 | 762 | public int GetHashCode(long[] obj) 763 | { 764 | var result = 17; 765 | for (var i = 0; i < obj.Length; i++) 766 | { 767 | unchecked 768 | { 769 | result = (int) (result * 23 + obj[i]); 770 | } 771 | } 772 | return result; 773 | } 774 | } 775 | } -------------------------------------------------------------------------------- /Runtime/FlexBuffer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0a155fbc8939b4ce7a93509900a04ba1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/FlexBufferBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace FlexBuffers 5 | { 6 | public class FlexBufferBuilder 7 | { 8 | public static byte[] Map(Action map) 9 | { 10 | var buffer = new FlexBuffer(); 11 | var start = buffer.StartVector(); 12 | var builder = new FlexBufferMapBuilder(buffer); 13 | map(builder); 14 | buffer.SortAndEndMap(start); 15 | return buffer.Finish(); 16 | } 17 | 18 | public static byte[] Vector(Action vector) 19 | { 20 | var buffer = new FlexBuffer(); 21 | var start = buffer.StartVector(); 22 | var builder = new FlexBufferVectorBuilder(buffer); 23 | vector(builder); 24 | buffer.EndVector(start, false, false); 25 | return buffer.Finish(); 26 | } 27 | } 28 | 29 | public interface IFlexBufferMapBuilder 30 | { 31 | void AddNull(string key); 32 | void Add(string key, long value, bool indirect = false); 33 | void Add(string key, long x, long y); 34 | void Add(string key, long x, long y, long z); 35 | void Add(string key, long x, long y, long z, long w); 36 | void Add(string key, ulong value, bool indirect = false); 37 | void Add(string key, ulong x, ulong y); 38 | void Add(string key, ulong x, ulong y, ulong z); 39 | void Add(string key, ulong x, ulong y, ulong z, ulong w); 40 | void Add(string key, double value, bool indirect = false); 41 | void Add(string key, double x, double y); 42 | void Add(string key, double x, double y, double z); 43 | void Add(string key, double x, double y, double z, double w); 44 | void Add(string key, bool value); 45 | void Add(string key, string value); 46 | void Add(string key, byte[] value); 47 | void Map(string key, Action map); 48 | void Vector(string key, Action vector); 49 | } 50 | 51 | public interface IFlexBufferVectorBuilder 52 | { 53 | void AddNull(); 54 | void Add(long value, bool indirect = false); 55 | void Add(long x, long y); 56 | void Add(long x, long y, long z); 57 | void Add(long x, long y, long z, long w); 58 | void Add(ulong value, bool indirect = false); 59 | void Add(ulong x, ulong y); 60 | void Add(ulong x, ulong y, ulong z); 61 | void Add(ulong x, ulong y, ulong z, ulong w); 62 | void Add(double value, bool indirect = false); 63 | void Add(double x, double y); 64 | void Add(double x, double y, double z); 65 | void Add(double x, double y, double z, double w); 66 | void Add(bool value); 67 | void Add(string value); 68 | void Add(byte[] value); 69 | void Map(Action map); 70 | void Vector(Action vector); 71 | } 72 | 73 | internal struct FlexBufferMapBuilder : IFlexBufferMapBuilder 74 | { 75 | private readonly FlexBuffer _buffer; 76 | 77 | internal FlexBufferMapBuilder(FlexBuffer buffer) 78 | { 79 | _buffer = buffer; 80 | } 81 | 82 | public void AddNull(string key) 83 | { 84 | _buffer.AddKey(key); 85 | _buffer.AddNull(); 86 | } 87 | 88 | public void Add(string key, long value, bool indirect = false) 89 | { 90 | _buffer.AddKey(key); 91 | if (indirect) 92 | { 93 | _buffer.AddIndirect(value); 94 | } 95 | else 96 | { 97 | _buffer.Add(value); 98 | } 99 | } 100 | 101 | public void Add(string key, long x, long y) 102 | { 103 | _buffer.AddKey(key); 104 | var start = _buffer.StartVector(); 105 | _buffer.Add(x); 106 | _buffer.Add(y); 107 | _buffer.EndVector(start, true, true); 108 | } 109 | 110 | public void Add(string key, long x, long y, long z) 111 | { 112 | _buffer.AddKey(key); 113 | var start = _buffer.StartVector(); 114 | _buffer.Add(x); 115 | _buffer.Add(y); 116 | _buffer.Add(z); 117 | _buffer.EndVector(start, true, true); 118 | } 119 | 120 | public void Add(string key, long x, long y, long z, long w) 121 | { 122 | _buffer.AddKey(key); 123 | var start = _buffer.StartVector(); 124 | _buffer.Add(x); 125 | _buffer.Add(y); 126 | _buffer.Add(z); 127 | _buffer.Add(w); 128 | _buffer.EndVector(start, true, true); 129 | } 130 | 131 | public void Add(string key, ulong value, bool indirect = false) 132 | { 133 | _buffer.AddKey(key); 134 | if (indirect) 135 | { 136 | _buffer.AddIndirect(value); 137 | } 138 | else 139 | { 140 | _buffer.Add(value); 141 | } 142 | } 143 | 144 | public void Add(string key, ulong x, ulong y) 145 | { 146 | _buffer.AddKey(key); 147 | var start = _buffer.StartVector(); 148 | _buffer.Add(x); 149 | _buffer.Add(y); 150 | _buffer.EndVector(start, true, true); 151 | } 152 | 153 | public void Add(string key, ulong x, ulong y, ulong z) 154 | { 155 | _buffer.AddKey(key); 156 | var start = _buffer.StartVector(); 157 | _buffer.Add(x); 158 | _buffer.Add(y); 159 | _buffer.Add(z); 160 | _buffer.EndVector(start, true, true); 161 | } 162 | 163 | public void Add(string key, ulong x, ulong y, ulong z, ulong w) 164 | { 165 | _buffer.AddKey(key); 166 | var start = _buffer.StartVector(); 167 | _buffer.Add(x); 168 | _buffer.Add(y); 169 | _buffer.Add(z); 170 | _buffer.Add(w); 171 | _buffer.EndVector(start, true, true); 172 | } 173 | 174 | public void Add(string key, double value, bool indirect = false) 175 | { 176 | _buffer.AddKey(key); 177 | if (indirect) 178 | { 179 | _buffer.AddIndirect(value); 180 | } 181 | else 182 | { 183 | _buffer.Add(value); 184 | } 185 | } 186 | 187 | public void Add(string key, double x, double y) 188 | { 189 | _buffer.AddKey(key); 190 | var start = _buffer.StartVector(); 191 | _buffer.Add(x); 192 | _buffer.Add(y); 193 | _buffer.EndVector(start, true, true); 194 | } 195 | 196 | public void Add(string key, double x, double y, double z) 197 | { 198 | _buffer.AddKey(key); 199 | var start = _buffer.StartVector(); 200 | _buffer.Add(x); 201 | _buffer.Add(y); 202 | _buffer.Add(z); 203 | _buffer.EndVector(start, true, true); 204 | } 205 | 206 | public void Add(string key, double x, double y, double z, double w) 207 | { 208 | _buffer.AddKey(key); 209 | var start = _buffer.StartVector(); 210 | _buffer.Add(x); 211 | _buffer.Add(y); 212 | _buffer.Add(z); 213 | _buffer.Add(w); 214 | _buffer.EndVector(start, true, true); 215 | } 216 | 217 | public void Add(string key, bool value) 218 | { 219 | _buffer.AddKey(key); 220 | _buffer.Add(value); 221 | } 222 | 223 | public void Add(string key, string value) 224 | { 225 | _buffer.AddKey(key); 226 | _buffer.Add(value); 227 | } 228 | 229 | public void Add(string key, byte[] value) 230 | { 231 | _buffer.AddKey(key); 232 | _buffer.Add(value); 233 | } 234 | 235 | public void Map(string key, Action map) 236 | { 237 | _buffer.AddKey(key); 238 | var start = _buffer.StartVector(); 239 | var builder = new FlexBufferMapBuilder(_buffer); 240 | map(builder); 241 | _buffer.SortAndEndMap(start); 242 | } 243 | 244 | public void Vector(string key, Action vector) 245 | { 246 | _buffer.AddKey(key); 247 | var start = _buffer.StartVector(); 248 | var builder = new FlexBufferVectorBuilder(_buffer); 249 | vector(builder); 250 | _buffer.EndVector(start, false, false); 251 | } 252 | } 253 | 254 | internal struct FlexBufferVectorBuilder : IFlexBufferVectorBuilder 255 | { 256 | private readonly FlexBuffer _buffer; 257 | 258 | internal FlexBufferVectorBuilder(FlexBuffer buffer) 259 | { 260 | _buffer = buffer; 261 | } 262 | 263 | public void AddNull() 264 | { 265 | _buffer.AddNull(); 266 | } 267 | public void Add(long value, bool indirect = false) 268 | { 269 | if (indirect) 270 | { 271 | _buffer.AddIndirect(value); 272 | } 273 | else 274 | { 275 | _buffer.Add(value); 276 | } 277 | } 278 | 279 | public void Add(long x, long y) 280 | { 281 | var start = _buffer.StartVector(); 282 | _buffer.Add(x); 283 | _buffer.Add(y); 284 | _buffer.EndVector(start, true, true); 285 | } 286 | 287 | public void Add(long x, long y, long z) 288 | { 289 | var start = _buffer.StartVector(); 290 | _buffer.Add(x); 291 | _buffer.Add(y); 292 | _buffer.Add(z); 293 | _buffer.EndVector(start, true, true); 294 | } 295 | 296 | public void Add(long x, long y, long z, long w) 297 | { 298 | var start = _buffer.StartVector(); 299 | _buffer.Add(x); 300 | _buffer.Add(y); 301 | _buffer.Add(z); 302 | _buffer.Add(w); 303 | _buffer.EndVector(start, true, true); 304 | } 305 | 306 | public void Add(ulong value, bool indirect = false) 307 | { 308 | if (indirect) 309 | { 310 | _buffer.AddIndirect(value); 311 | } 312 | else 313 | { 314 | _buffer.Add(value); 315 | } 316 | } 317 | 318 | public void Add(ulong x, ulong y) 319 | { 320 | var start = _buffer.StartVector(); 321 | _buffer.Add(x); 322 | _buffer.Add(y); 323 | _buffer.EndVector(start, true, true); 324 | } 325 | 326 | public void Add(ulong x, ulong y, ulong z) 327 | { 328 | var start = _buffer.StartVector(); 329 | _buffer.Add(x); 330 | _buffer.Add(y); 331 | _buffer.Add(z); 332 | _buffer.EndVector(start, true, true); 333 | } 334 | 335 | public void Add(ulong x, ulong y, ulong z, ulong w) 336 | { 337 | var start = _buffer.StartVector(); 338 | _buffer.Add(x); 339 | _buffer.Add(y); 340 | _buffer.Add(z); 341 | _buffer.Add(w); 342 | _buffer.EndVector(start, true, true); 343 | } 344 | 345 | public void Add(double value, bool indirect = false) 346 | { 347 | if (indirect) 348 | { 349 | _buffer.AddIndirect(value); 350 | } 351 | else 352 | { 353 | _buffer.Add(value); 354 | } 355 | } 356 | 357 | public void Add(double x, double y) 358 | { 359 | var start = _buffer.StartVector(); 360 | _buffer.Add(x); 361 | _buffer.Add(y); 362 | _buffer.EndVector(start, true, true); 363 | } 364 | 365 | public void Add(double x, double y, double z) 366 | { 367 | var start = _buffer.StartVector(); 368 | _buffer.Add(x); 369 | _buffer.Add(y); 370 | _buffer.Add(z); 371 | _buffer.EndVector(start, true, true); 372 | } 373 | 374 | public void Add(double x, double y, double z, double w) 375 | { 376 | var start = _buffer.StartVector(); 377 | _buffer.Add(x); 378 | _buffer.Add(y); 379 | _buffer.Add(z); 380 | _buffer.Add(w); 381 | _buffer.EndVector(start, true, true); 382 | } 383 | 384 | public void Add(bool value) 385 | { 386 | _buffer.Add(value); 387 | } 388 | 389 | public void Add(string value) 390 | { 391 | _buffer.Add(value); 392 | } 393 | 394 | public void Add(byte[] value) 395 | { 396 | _buffer.Add(value); 397 | } 398 | 399 | public void Map(Action map) 400 | { 401 | var start = _buffer.StartVector(); 402 | var builder = new FlexBufferMapBuilder(_buffer); 403 | map(builder); 404 | _buffer.SortAndEndMap(start); 405 | } 406 | 407 | public void Vector(Action vector) 408 | { 409 | var start = _buffer.StartVector(); 410 | var builder = new FlexBufferVectorBuilder(_buffer); 411 | vector(builder); 412 | _buffer.EndVector(start, false, false); 413 | } 414 | } 415 | } -------------------------------------------------------------------------------- /Runtime/FlexBufferBuilder.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 18996b34270544aeb82ec21f754ab660 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/FlxValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Globalization; 5 | using System.Text; 6 | 7 | namespace FlexBuffers 8 | { 9 | public struct FlxValue 10 | { 11 | private readonly byte[] _buffer; 12 | private readonly int _offset; 13 | private readonly byte _parentWidth; 14 | private readonly byte _byteWidth; 15 | private readonly Type _type; 16 | 17 | internal FlxValue(byte[] buffer, int offset, byte parentWidth, byte packedType) 18 | { 19 | _buffer = buffer; 20 | _offset = offset; 21 | _parentWidth = parentWidth; 22 | _byteWidth = (byte) (1 << (packedType & 3)); 23 | _type = (Type) (packedType >> 2); 24 | } 25 | 26 | internal FlxValue(byte[] buffer, int offset, byte parentWidth, byte byteWidth, Type type) 27 | { 28 | _buffer = buffer; 29 | _offset = offset; 30 | _parentWidth = parentWidth; 31 | _byteWidth = byteWidth; 32 | _type = type; 33 | } 34 | 35 | public static FlxValue FromBytes(byte[] bytes) 36 | { 37 | if (bytes.Length < 3) 38 | { 39 | throw new Exception($"Invalid buffer {bytes}"); 40 | } 41 | 42 | var byteWidth = bytes[bytes.Length - 1]; 43 | var packedType = bytes[bytes.Length - 2]; 44 | var offset = bytes.Length - byteWidth - 2; 45 | return new FlxValue(bytes, offset, byteWidth, packedType); 46 | } 47 | 48 | public Type ValueType => _type; 49 | public int BufferOffset => _offset; 50 | 51 | public bool IsNull => _type == Type.Null; 52 | 53 | public long AsLong 54 | { 55 | get 56 | { 57 | if (_type == Type.Int) 58 | { 59 | return ReadLong(_buffer, _offset, _parentWidth); 60 | } 61 | 62 | if (_type == Type.IndirectInt) 63 | { 64 | var indirectOffset = ComputeIndirectOffset(_buffer, _offset, _parentWidth); 65 | return ReadLong(_buffer, indirectOffset, _byteWidth); 66 | } 67 | 68 | if (_type == Type.Uint) 69 | { 70 | var value = ReadULong(_buffer, _offset, _parentWidth); 71 | if (value <= long.MaxValue) 72 | { 73 | return (long) value; 74 | } 75 | } 76 | if (_type == Type.IndirectUInt) 77 | { 78 | var indirectOffset = ComputeIndirectOffset(_buffer, _offset, _parentWidth); 79 | var value = ReadULong(_buffer, indirectOffset, _byteWidth); 80 | if (value <= long.MaxValue) 81 | { 82 | return (long) value; 83 | } 84 | } 85 | throw new Exception($"Type {_type} is not convertible to long"); 86 | } 87 | } 88 | 89 | public ulong AsULong 90 | { 91 | get 92 | { 93 | if (_type == Type.Uint) 94 | { 95 | return ReadULong(_buffer, _offset, _parentWidth); 96 | } 97 | 98 | if (_type == Type.IndirectUInt) 99 | { 100 | var indirectOffset = ComputeIndirectOffset(_buffer, _offset, _parentWidth); 101 | return ReadULong(_buffer, indirectOffset, _byteWidth); 102 | } 103 | 104 | if (_type == Type.Int) 105 | { 106 | var value = ReadLong(_buffer, _offset, _parentWidth); 107 | if (value >= 0) 108 | { 109 | return (ulong) value; 110 | } 111 | } 112 | 113 | if (_type == Type.IndirectInt) 114 | { 115 | var indirectOffset = ComputeIndirectOffset(_buffer, _offset, _parentWidth); 116 | var value = ReadLong(_buffer, indirectOffset, _byteWidth); 117 | if (value >= 0) 118 | { 119 | return (ulong) value; 120 | } 121 | } 122 | throw new Exception($"Type {_type} is not convertible to ulong"); 123 | } 124 | } 125 | 126 | public double AsDouble 127 | { 128 | get 129 | { 130 | if (_type == Type.Float) 131 | { 132 | return ReadDouble(_buffer, _offset, _parentWidth); 133 | } 134 | if (_type == Type.Int) 135 | { 136 | return ReadLong(_buffer, _offset, _parentWidth); 137 | } 138 | if (_type == Type.Uint) 139 | { 140 | return ReadULong(_buffer, _offset, _parentWidth); 141 | } 142 | if (_type == Type.IndirectFloat) 143 | { 144 | var indirectOffset = ComputeIndirectOffset(_buffer, _offset, _parentWidth); 145 | return ReadDouble(_buffer, indirectOffset, _byteWidth); 146 | } 147 | if (_type == Type.IndirectUInt) 148 | { 149 | var indirectOffset = ComputeIndirectOffset(_buffer, _offset, _parentWidth); 150 | return ReadULong(_buffer, indirectOffset, _byteWidth); 151 | } 152 | if (_type == Type.IndirectInt) 153 | { 154 | var indirectOffset = ComputeIndirectOffset(_buffer, _offset, _parentWidth); 155 | return ReadLong(_buffer, indirectOffset, _byteWidth); 156 | } 157 | throw new Exception($"Type {_type} is not convertible to double"); 158 | } 159 | } 160 | 161 | public bool AsBool 162 | { 163 | get 164 | { 165 | if (_type == Type.Bool) 166 | { 167 | return _buffer[_offset] != 0; 168 | } 169 | if (_type == Type.Int) 170 | { 171 | return ReadLong(_buffer, _offset, _parentWidth) != 0; 172 | } 173 | if (_type == Type.Uint) 174 | { 175 | return ReadULong(_buffer, _offset, _parentWidth) != 0; 176 | } 177 | throw new Exception($"Type {_type} is not convertible to bool"); 178 | } 179 | } 180 | 181 | public string AsString 182 | { 183 | get 184 | { 185 | if (_type == Type.String) 186 | { 187 | var indirectOffset = ComputeIndirectOffset(_buffer, _offset, _parentWidth); 188 | var size = (int)ReadULong(_buffer, indirectOffset - _byteWidth, _byteWidth); 189 | var sizeWidth = (int)_byteWidth; 190 | while (_buffer[indirectOffset + size] != 0) 191 | { 192 | sizeWidth <<= 1; 193 | size = (int)ReadULong(_buffer, indirectOffset - sizeWidth, (byte)sizeWidth); 194 | } 195 | 196 | return Encoding.UTF8.GetString(_buffer, indirectOffset, size); 197 | } 198 | 199 | if (_type == Type.Key) 200 | { 201 | var indirectOffset = ComputeIndirectOffset(_buffer, _offset, _parentWidth); 202 | var size = 0; 203 | while (indirectOffset + size < _buffer.Length && _buffer[indirectOffset + size] != 0) 204 | { 205 | size++; 206 | } 207 | return Encoding.UTF8.GetString(_buffer, indirectOffset, size); 208 | } 209 | 210 | throw new Exception($"Type {_type} is not convertible to string"); 211 | } 212 | } 213 | 214 | public FlxValue this[int index] => AsVector[index]; 215 | 216 | public FlxValue this[string key] => AsMap[key]; 217 | 218 | public FlxVector AsVector 219 | { 220 | get 221 | { 222 | if (TypesUtil.IsAVector(_type) == false) 223 | { 224 | throw new Exception($"Type {_type} is not a vector."); 225 | } 226 | 227 | var indirectOffset = ComputeIndirectOffset(_buffer, _offset, _parentWidth); 228 | var size = TypesUtil.IsFixedTypedVector(_type) 229 | ? TypesUtil.FixedTypedVectorElementSize(_type) 230 | : (int)ReadULong(_buffer, indirectOffset - _byteWidth, _byteWidth); 231 | return new FlxVector(_buffer, indirectOffset, _byteWidth, _type, size); 232 | } 233 | } 234 | 235 | public FlxMap AsMap 236 | { 237 | get 238 | { 239 | if (_type != Type.Map) 240 | { 241 | throw new Exception($"Type {_type} is not a map."); 242 | } 243 | 244 | var indirectOffset = ComputeIndirectOffset(_buffer, _offset, _parentWidth); 245 | var size = ReadULong(_buffer, indirectOffset - _byteWidth, _byteWidth); 246 | return new FlxMap(_buffer, indirectOffset, _byteWidth, (int)size); 247 | } 248 | } 249 | 250 | public byte[] AsBlob 251 | { 252 | get 253 | { 254 | if (_type != Type.Blob) 255 | { 256 | throw new Exception($"Type {_type} is not a blob."); 257 | } 258 | var indirectOffset = ComputeIndirectOffset(_buffer, _offset, _parentWidth); 259 | var size = ReadULong(_buffer, indirectOffset - _byteWidth, _byteWidth); 260 | var blob = new byte[size]; 261 | System.Buffer.BlockCopy(_buffer, indirectOffset, blob, 0, (int)size); 262 | return blob; 263 | } 264 | } 265 | 266 | public string ToJson 267 | { 268 | get 269 | { 270 | if (IsNull) 271 | { 272 | return "null"; 273 | } 274 | 275 | if (_type == Type.Bool) 276 | { 277 | return AsBool ? "true" : "false"; 278 | } 279 | 280 | if (_type == Type.Int || _type == Type.IndirectInt) 281 | { 282 | return AsLong.ToString(); 283 | } 284 | 285 | if (_type == Type.Uint || _type == Type.IndirectUInt) 286 | { 287 | return AsULong.ToString(); 288 | } 289 | 290 | if (_type == Type.Float || _type == Type.IndirectFloat) 291 | { 292 | return AsDouble.ToString(CultureInfo.CurrentCulture); 293 | } 294 | 295 | if (TypesUtil.IsAVector(_type)) 296 | { 297 | return AsVector.ToJson; 298 | } 299 | 300 | if (_type == Type.String || _type == Type.Key) 301 | { 302 | var jsonConformString = AsString.Replace("\"", "\\\"") 303 | .Replace("\n", "\\n") 304 | .Replace("\r", "\\r") 305 | .Replace("\t", "\\t") 306 | .Replace("/", "\\/"); 307 | return $"\"{jsonConformString}\""; 308 | } 309 | 310 | if (_type == Type.Map) 311 | { 312 | return AsMap.ToJson; 313 | } 314 | 315 | if (_type == Type.Blob) 316 | { 317 | return $"\"{Convert.ToBase64String(AsBlob)}\""; 318 | } 319 | 320 | throw new Exception($"Unexpected type {_type}"); 321 | } 322 | } 323 | 324 | public string ToPrettyJson(string left = "", bool childrenOnly = false) 325 | { 326 | if (_type == Type.Map) 327 | { 328 | return AsMap.ToPrettyJson(left, childrenOnly); 329 | } 330 | if (TypesUtil.IsAVector(_type)) 331 | { 332 | return AsVector.ToPrettyJson(left, childrenOnly); 333 | } 334 | 335 | if (childrenOnly) 336 | { 337 | return ToJson; 338 | } 339 | 340 | return $"{left}{ToJson}"; 341 | } 342 | 343 | internal static long ReadLong(byte[] bytes, int offset, byte width) 344 | { 345 | if (offset < 0 || bytes.Length <= (offset + width) || (offset & (width - 1)) != 0) 346 | { 347 | throw new Exception("Bad offset"); 348 | } 349 | 350 | if (width == 1) 351 | { 352 | return (sbyte)bytes[offset]; 353 | } 354 | 355 | if (width == 2) 356 | { 357 | return BitConverter.ToInt16(bytes, offset); 358 | } 359 | 360 | if (width == 4) 361 | { 362 | return BitConverter.ToInt32(bytes, offset); 363 | } 364 | 365 | return BitConverter.ToInt64(bytes, offset); 366 | } 367 | 368 | internal static ulong ReadULong(byte[] bytes, int offset, byte width) 369 | { 370 | if (offset < 0 || bytes.Length <= (offset + width) || (offset & (width - 1)) != 0) 371 | { 372 | throw new Exception("Bad offset"); 373 | } 374 | 375 | if (width == 1) 376 | { 377 | return bytes[offset]; 378 | } 379 | 380 | if (width == 2) 381 | { 382 | return BitConverter.ToUInt16(bytes, offset); 383 | } 384 | 385 | if (width == 4) 386 | { 387 | return BitConverter.ToUInt32(bytes, offset); 388 | } 389 | 390 | return BitConverter.ToUInt64(bytes, offset); 391 | } 392 | 393 | internal static double ReadDouble(byte[] bytes, int offset, byte width) 394 | { 395 | if (offset < 0 || bytes.Length <= (offset + width) || (offset & (width - 1)) != 0) 396 | { 397 | throw new Exception("Bad offset"); 398 | } 399 | 400 | if (width != 4 && width != 8) 401 | { 402 | throw new Exception($"Bad width {width}"); 403 | } 404 | 405 | if (width == 4) 406 | { 407 | return BitConverter.ToSingle(bytes, offset); 408 | } 409 | 410 | return BitConverter.ToDouble(bytes, offset); 411 | } 412 | 413 | internal static int ComputeIndirectOffset(byte[] bytes, int offset, byte width) 414 | { 415 | var step = (int)ReadULong(bytes, offset, width); 416 | return offset - step; 417 | } 418 | 419 | internal byte[] Buffer => _buffer; 420 | internal int Offset => _offset; 421 | 422 | internal int IndirectOffset => ComputeIndirectOffset(_buffer, _offset, _parentWidth); 423 | 424 | } 425 | 426 | public struct FlxVector: IEnumerable 427 | { 428 | private readonly byte[] _buffer; 429 | private readonly int _offset; 430 | private readonly int _length; 431 | private readonly byte _byteWidth; 432 | private readonly Type _type; 433 | 434 | internal FlxVector(byte[] buffer, int offset, byte byteWidth, Type type, int length) 435 | { 436 | _buffer = buffer; 437 | _offset = offset; 438 | _byteWidth = byteWidth; 439 | _type = type; 440 | _length = length; 441 | } 442 | 443 | public int Length => _length; 444 | 445 | public FlxValue this[int index] 446 | { 447 | get 448 | { 449 | if (index < 0 || index >= _length) 450 | { 451 | throw new Exception($"Bad index {index}, should be 0...{_length}"); 452 | } 453 | 454 | if (TypesUtil.IsTypedVector(_type)) 455 | { 456 | var elemOffset = _offset + (index * _byteWidth); 457 | return new FlxValue(_buffer, elemOffset, _byteWidth, 1, TypesUtil.TypedVectorElementType(_type)); 458 | } 459 | 460 | if (TypesUtil.IsFixedTypedVector(_type)) 461 | { 462 | var elemOffset = _offset + (index * _byteWidth); 463 | return new FlxValue(_buffer, elemOffset, _byteWidth, 1, TypesUtil.FixedTypedVectorElementType(_type)); 464 | } 465 | 466 | if (_type == Type.Vector) 467 | { 468 | var packedType = _buffer[_offset + _length * _byteWidth + index]; 469 | var elemOffset = _offset + (index * _byteWidth); 470 | return new FlxValue(_buffer, elemOffset, _byteWidth, packedType); 471 | } 472 | throw new Exception($"Bad index {index}, should be 0...{_length}"); 473 | } 474 | } 475 | 476 | public string ToJson 477 | { 478 | get 479 | { 480 | var builder = new StringBuilder(); 481 | builder.Append("["); 482 | for (var i = 0; i < _length; i++) 483 | { 484 | builder.Append(this[i].ToJson); 485 | if (i < _length - 1) 486 | { 487 | builder.Append(","); 488 | } 489 | } 490 | 491 | builder.Append("]"); 492 | 493 | return builder.ToString(); 494 | } 495 | } 496 | 497 | public string ToPrettyJson(string left = "", bool childrenOnly = false) 498 | { 499 | var builder = new StringBuilder(); 500 | if (childrenOnly == false) 501 | { 502 | builder.Append(left); 503 | } 504 | 505 | builder.Append("[\n"); 506 | for (var i = 0; i < _length; i++) 507 | { 508 | builder.Append(this[i].ToPrettyJson($"{left} ")); 509 | if (i < _length - 1) 510 | { 511 | builder.Append(","); 512 | } 513 | 514 | builder.Append("\n"); 515 | } 516 | builder.Append(left); 517 | builder.Append("]"); 518 | 519 | return builder.ToString(); 520 | } 521 | 522 | public IEnumerator GetEnumerator() 523 | { 524 | for (var i = 0; i < _length; i++) 525 | { 526 | yield return this[i]; 527 | } 528 | } 529 | 530 | IEnumerator IEnumerable.GetEnumerator() 531 | { 532 | return GetEnumerator(); 533 | } 534 | } 535 | 536 | public struct FlxMap: IEnumerable> 537 | { 538 | private readonly byte[] _buffer; 539 | private readonly int _offset; 540 | private readonly int _length; 541 | private readonly byte _byteWidth; 542 | 543 | internal FlxMap(byte[] buffer, int offset, byte byteWidth, int length) 544 | { 545 | _buffer = buffer; 546 | _offset = offset; 547 | _byteWidth = byteWidth; 548 | _length = length; 549 | } 550 | 551 | public int Length => _length; 552 | 553 | private FlxVector Keys 554 | { 555 | get 556 | { 557 | var keysOffset = _offset - _byteWidth * 3; 558 | var indirectOffset = FlxValue.ComputeIndirectOffset(_buffer, keysOffset, _byteWidth); 559 | var bWidth = FlxValue.ReadULong(_buffer, keysOffset + _byteWidth, _byteWidth); 560 | return new FlxVector(_buffer, indirectOffset, (byte) bWidth, Type.VectorKey, _length); 561 | } 562 | } 563 | 564 | private FlxVector Values => new FlxVector(_buffer, _offset, _byteWidth, Type.Vector, _length); 565 | 566 | public FlxValue this[string key] 567 | { 568 | get 569 | { 570 | var index = KeyIndex(key); 571 | if (index < 0) 572 | { 573 | throw new Exception($"No key '{key}' could be found"); 574 | } 575 | return Values[index]; 576 | } 577 | } 578 | 579 | public FlxValue ValueByIndex(int keyIndex) 580 | { 581 | if (keyIndex < 0 || keyIndex >= Length) 582 | { 583 | throw new Exception($"Bad Key index {keyIndex}"); 584 | } 585 | 586 | return Values[keyIndex]; 587 | } 588 | 589 | public string ToJson 590 | { 591 | get 592 | { 593 | var builder = new StringBuilder(); 594 | builder.Append("{"); 595 | var keys = Keys; 596 | var values = Values; 597 | for (var i = 0; i < _length; i++) 598 | { 599 | builder.Append($"{keys[i].ToJson}:{values[i].ToJson}"); 600 | if (i < _length - 1) 601 | { 602 | builder.Append(","); 603 | } 604 | } 605 | builder.Append("}"); 606 | return builder.ToString(); 607 | } 608 | } 609 | 610 | public string ToPrettyJson(string left = "", bool childrenOnly = false) 611 | { 612 | var builder = new StringBuilder(); 613 | if (childrenOnly == false) 614 | { 615 | builder.Append(left); 616 | } 617 | builder.Append("{\n"); 618 | var keys = Keys; 619 | var values = Values; 620 | for (var i = 0; i < _length; i++) 621 | { 622 | builder.Append($"{left} {keys[i].ToPrettyJson()} : {values[i].ToPrettyJson($"{left} ", true)}"); 623 | if (i < _length - 1) 624 | { 625 | builder.Append(","); 626 | } 627 | 628 | builder.Append("\n"); 629 | } 630 | builder.Append(left); 631 | builder.Append("}"); 632 | return builder.ToString(); 633 | } 634 | 635 | public int KeyIndex(string key) 636 | { 637 | var keyBytes = Encoding.UTF8.GetBytes(key); 638 | var low = 0; 639 | var high = _length - 1; 640 | while (low <= high) 641 | { 642 | var mid = (high + low) >> 1; 643 | var dif = Comp(mid, keyBytes); 644 | if (dif == 0) 645 | { 646 | return mid; 647 | } 648 | if (dif < 0) 649 | { 650 | high = mid - 1; 651 | } else 652 | { 653 | low = mid + 1; 654 | } 655 | } 656 | 657 | return -1; 658 | } 659 | 660 | private int Comp(int i, string key) 661 | { 662 | // TODO: keep it so we can profile it against byte comparison 663 | var key2 = Keys[i].AsString; 664 | return string.Compare(key, key2, StringComparison.Ordinal); 665 | } 666 | 667 | private int Comp(int i, byte[] key) 668 | { 669 | var key2 = Keys[i]; 670 | var indirectOffset = key2.IndirectOffset; 671 | for (int j = 0; j < key.Length; j++) 672 | { 673 | var dif = key[j] - key2.Buffer[indirectOffset + j]; 674 | if (dif != 0) 675 | { 676 | return dif; 677 | } 678 | } 679 | // keys are zero terminated 680 | return key2.Buffer[indirectOffset + key.Length] == 0 ? 0 : -1; 681 | } 682 | 683 | public IEnumerator> GetEnumerator() 684 | { 685 | var keys = Keys; 686 | var values = Values; 687 | for (var i = 0; i < _length; i++) 688 | { 689 | yield return new KeyValuePair(keys[i].AsString, values[i]); 690 | } 691 | } 692 | 693 | IEnumerator IEnumerable.GetEnumerator() 694 | { 695 | return GetEnumerator(); 696 | } 697 | } 698 | } -------------------------------------------------------------------------------- /Runtime/FlxValue.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5d4584c3c33584ca19e5e590aef0b225 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/JsonToFlexBufferConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using System.Globalization; 5 | 6 | // This code is based on LightJson project. 7 | // https://github.com/MarcosLopezC/LightJson 8 | // Big thanks goes to Marcos Vladimir López Castellanos 9 | // https://github.com/MarcosLopezC 10 | namespace FlexBuffers 11 | { 12 | internal struct TextPosition 13 | { 14 | public long column; 15 | public long line; 16 | 17 | public override string ToString() 18 | { 19 | return $"[line:{line} column:{column}]"; 20 | } 21 | } 22 | 23 | internal sealed class TextScanner 24 | { 25 | private readonly TextReader _reader; 26 | private TextPosition _position; 27 | 28 | public TextPosition Position => _position; 29 | 30 | public bool CanRead => (_reader.Peek() != -1); 31 | 32 | internal TextScanner(TextReader reader) 33 | { 34 | _reader = reader ?? throw new ArgumentNullException(nameof(reader)); 35 | } 36 | 37 | internal char Peek() 38 | { 39 | var next = _reader.Peek(); 40 | 41 | if (next == -1) 42 | { 43 | throw new Exception($"Incomplete message {_position}"); 44 | } 45 | 46 | return (char)next; 47 | } 48 | 49 | internal char Read() 50 | { 51 | var next = _reader.Read(); 52 | 53 | if (next == -1) 54 | { 55 | throw new Exception($"Incomplete message {_position}"); 56 | } 57 | 58 | switch (next) 59 | { 60 | case '\r': 61 | // Normalize '\r\n' line encoding to '\n'. 62 | if (_reader.Peek() == '\n') 63 | { 64 | _reader.Read(); 65 | } 66 | goto case '\n'; 67 | 68 | case '\n': 69 | _position.line += 1; 70 | _position.column = 0; 71 | return '\n'; 72 | 73 | default: 74 | _position.column += 1; 75 | return (char)next; 76 | } 77 | } 78 | 79 | internal void SkipWhitespace() 80 | { 81 | while (char.IsWhiteSpace(Peek())) 82 | { 83 | Read(); 84 | } 85 | } 86 | 87 | internal void Assert(char next) 88 | { 89 | if (Peek() == next) 90 | { 91 | Read(); 92 | } 93 | else 94 | { 95 | throw new Exception($"Parser expected {next} at position {_position}"); 96 | } 97 | } 98 | 99 | public void Assert(string next) 100 | { 101 | for (var i = 0; i < next.Length; i += 1) 102 | { 103 | Assert(next[i]); 104 | } 105 | } 106 | } 107 | 108 | public class JsonToFlexBufferConverter 109 | { 110 | private readonly TextScanner _scanner; 111 | 112 | private JsonToFlexBufferConverter(TextReader reader) 113 | { 114 | _scanner = new TextScanner(reader); 115 | } 116 | public static byte[] Convert(TextReader reader, FlexBuffer.Options options = FlexBuffer.Options.ShareKeys | FlexBuffer.Options.ShareStrings | FlexBuffer.Options.ShareKeyVectors) 117 | { 118 | if (reader == null) 119 | { 120 | throw new ArgumentNullException(nameof(reader)); 121 | } 122 | var flx = new FlexBuffer(options:options); 123 | new JsonToFlexBufferConverter(reader).ReadJsonValue(flx); 124 | return flx.Finish(); 125 | } 126 | 127 | public static byte[] ConvertFile(string path, FlexBuffer.Options options = FlexBuffer.Options.ShareKeys | FlexBuffer.Options.ShareStrings | FlexBuffer.Options.ShareKeyVectors) 128 | { 129 | if (path == null) 130 | { 131 | throw new ArgumentNullException(nameof(path)); 132 | } 133 | 134 | using (var reader = new StreamReader(path)) 135 | { 136 | return Convert(reader, options); 137 | } 138 | } 139 | 140 | public static byte[] Convert(string source, FlexBuffer.Options options = FlexBuffer.Options.ShareKeys | FlexBuffer.Options.ShareStrings | FlexBuffer.Options.ShareKeyVectors) 141 | { 142 | if (source == null) 143 | { 144 | throw new ArgumentNullException(nameof(source)); 145 | } 146 | 147 | using (var reader = new StringReader(source)) 148 | { 149 | return Convert(reader, options); 150 | } 151 | } 152 | 153 | private void ReadJsonValue(FlexBuffer flx) 154 | { 155 | _scanner.SkipWhitespace(); 156 | 157 | var next = _scanner.Peek(); 158 | 159 | if (char.IsNumber(next)) 160 | { 161 | ReadNumber(flx); 162 | return; 163 | } 164 | 165 | switch (next) 166 | { 167 | case '{': 168 | ReadObject(flx); 169 | return; 170 | 171 | case '[': 172 | ReadArray(flx); 173 | return; 174 | 175 | case '"': 176 | ReadString(flx); 177 | return; 178 | 179 | case '-': 180 | ReadNumber(flx); 181 | return; 182 | 183 | case 't': 184 | case 'f': 185 | ReadBoolean(flx); 186 | return; 187 | 188 | case 'n': 189 | ReadNull(flx); 190 | return; 191 | 192 | default: 193 | throw new Exception($"Unexpected character {_scanner.Position}"); 194 | } 195 | } 196 | 197 | private void ReadNull(FlexBuffer flx) 198 | { 199 | _scanner.Assert("null"); 200 | flx.AddNull(); 201 | } 202 | 203 | private void ReadBoolean(FlexBuffer flx) 204 | { 205 | switch (_scanner.Peek()) 206 | { 207 | case 't': 208 | _scanner.Assert("true"); 209 | flx.Add(true); 210 | return; 211 | 212 | case 'f': 213 | _scanner.Assert("false"); 214 | flx.Add(false); 215 | return; 216 | 217 | default: 218 | throw new Exception($"Unexpected character {_scanner.Position}"); 219 | } 220 | } 221 | 222 | private void ReadDigits(StringBuilder builder) 223 | { 224 | while (_scanner.CanRead && char.IsDigit(_scanner.Peek())) 225 | { 226 | builder.Append(_scanner.Read()); 227 | } 228 | } 229 | 230 | private void ReadNumber(FlexBuffer flx) 231 | { 232 | var builder = new StringBuilder(); 233 | 234 | var isFloat = false; 235 | 236 | if (_scanner.Peek() == '-') 237 | { 238 | builder.Append(_scanner.Read()); 239 | } 240 | 241 | if (_scanner.Peek() == '0') 242 | { 243 | builder.Append(_scanner.Read()); 244 | } 245 | else 246 | { 247 | ReadDigits(builder); 248 | } 249 | 250 | if (_scanner.CanRead && _scanner.Peek() == '.') 251 | { 252 | builder.Append(_scanner.Read()); 253 | ReadDigits(builder); 254 | isFloat = true; 255 | } 256 | 257 | if (_scanner.CanRead && char.ToLowerInvariant(_scanner.Peek()) == 'e') 258 | { 259 | builder.Append(_scanner.Read()); 260 | 261 | var next = _scanner.Peek(); 262 | 263 | switch (next) 264 | { 265 | case '+': 266 | case '-': 267 | builder.Append(_scanner.Read()); 268 | break; 269 | } 270 | 271 | ReadDigits(builder); 272 | } 273 | 274 | if (isFloat) 275 | { 276 | var value = double.Parse( 277 | builder.ToString(), 278 | CultureInfo.InvariantCulture 279 | ); 280 | flx.Add(value); 281 | } 282 | else 283 | { 284 | flx.Add(long.Parse(builder.ToString())); 285 | } 286 | } 287 | 288 | private void ReadString(FlexBuffer flx, bool asKey = false) 289 | { 290 | var builder = new StringBuilder(); 291 | 292 | _scanner.Assert('"'); 293 | 294 | while (true) 295 | { 296 | var c = _scanner.Read(); 297 | 298 | if (c == '\\') 299 | { 300 | c = _scanner.Read(); 301 | 302 | switch (char.ToLower(c)) 303 | { 304 | case '"': // " 305 | case '\\': // \ 306 | case '/': // / 307 | builder.Append(c); 308 | break; 309 | case 'b': 310 | builder.Append('\b'); 311 | break; 312 | case 'f': 313 | builder.Append('\f'); 314 | break; 315 | case 'n': 316 | builder.Append('\n'); 317 | break; 318 | case 'r': 319 | builder.Append('\r'); 320 | break; 321 | case 't': 322 | builder.Append('\t'); 323 | break; 324 | case 'u': 325 | builder.Append(ReadUnicodeLiteral()); 326 | break; 327 | default: 328 | throw new Exception($"Unexpected character {_scanner.Position}"); 329 | } 330 | } 331 | else if (c == '"') 332 | { 333 | break; 334 | } 335 | else 336 | { 337 | if (char.IsControl(c)) 338 | { 339 | throw new Exception($"Unexpected character {_scanner.Position}"); 340 | } 341 | else 342 | { 343 | builder.Append(c); 344 | } 345 | } 346 | } 347 | 348 | if (asKey) 349 | { 350 | flx.AddKey(builder.ToString()); 351 | } 352 | else 353 | { 354 | flx.Add(builder.ToString()); 355 | } 356 | } 357 | 358 | private int ReadHexDigit() 359 | { 360 | switch (char.ToUpper(_scanner.Read())) 361 | { 362 | case '0': 363 | return 0; 364 | 365 | case '1': 366 | return 1; 367 | 368 | case '2': 369 | return 2; 370 | 371 | case '3': 372 | return 3; 373 | 374 | case '4': 375 | return 4; 376 | 377 | case '5': 378 | return 5; 379 | 380 | case '6': 381 | return 6; 382 | 383 | case '7': 384 | return 7; 385 | 386 | case '8': 387 | return 8; 388 | 389 | case '9': 390 | return 9; 391 | 392 | case 'A': 393 | return 10; 394 | 395 | case 'B': 396 | return 11; 397 | 398 | case 'C': 399 | return 12; 400 | 401 | case 'D': 402 | return 13; 403 | 404 | case 'E': 405 | return 14; 406 | 407 | case 'F': 408 | return 15; 409 | 410 | default: 411 | throw new Exception($"Unexpected character {_scanner.Position}"); 412 | } 413 | } 414 | 415 | private char ReadUnicodeLiteral() 416 | { 417 | int value = 0; 418 | 419 | value += ReadHexDigit() * 4096; // 16^3 420 | value += ReadHexDigit() * 256; // 16^2 421 | value += ReadHexDigit() * 16; // 16^1 422 | value += ReadHexDigit(); // 16^0 423 | 424 | return (char)value; 425 | } 426 | 427 | private void ReadArray(FlexBuffer flx) 428 | { 429 | _scanner.Assert('['); 430 | 431 | var start = flx.StartVector(); 432 | 433 | _scanner.SkipWhitespace(); 434 | 435 | if (_scanner.Peek() == ']') 436 | { 437 | _scanner.Read(); 438 | } 439 | else 440 | { 441 | while (true) 442 | { 443 | ReadJsonValue(flx); 444 | 445 | _scanner.SkipWhitespace(); 446 | 447 | var next = _scanner.Read(); 448 | 449 | if (next == ']') 450 | { 451 | break; 452 | } 453 | 454 | if (next == ',') 455 | { 456 | continue; 457 | } 458 | 459 | throw new Exception($"Unexpected character {next} at position {_scanner.Position}"); 460 | } 461 | } 462 | 463 | flx.EndVector(start, false, false); 464 | } 465 | 466 | private void ReadObject(FlexBuffer flx) 467 | { 468 | _scanner.Assert('{'); 469 | 470 | _scanner.SkipWhitespace(); 471 | 472 | var start = flx.StartVector(); 473 | 474 | if (_scanner.Peek() == '}') 475 | { 476 | _scanner.Read(); 477 | } 478 | else 479 | { 480 | while (true) 481 | { 482 | _scanner.SkipWhitespace(); 483 | 484 | ReadString(flx, true); 485 | 486 | _scanner.SkipWhitespace(); 487 | 488 | _scanner.Assert(':'); 489 | 490 | _scanner.SkipWhitespace(); 491 | 492 | ReadJsonValue(flx); 493 | 494 | _scanner.SkipWhitespace(); 495 | 496 | var next = _scanner.Read(); 497 | 498 | if (next == '}') 499 | { 500 | break; 501 | } 502 | 503 | if (next == ',') 504 | { 505 | continue; 506 | } 507 | throw new Exception($"Unexpected character {next} at position {_scanner.Position}"); 508 | } 509 | } 510 | 511 | flx.SortAndEndMap(start); 512 | } 513 | } 514 | } -------------------------------------------------------------------------------- /Runtime/JsonToFlexBufferConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 47d8a84a248d4436b9b918ab7bb3685b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/StackValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace FlexBuffers 5 | { 6 | [StructLayout(LayoutKind.Explicit, Size=10)] 7 | public struct StackValue 8 | { 9 | [FieldOffset(0)] private ulong UValue; 10 | 11 | [FieldOffset(0)] private long LValue; 12 | 13 | [FieldOffset(0)] private double DValue; 14 | 15 | [FieldOffset(8)] private BitWidth Width; 16 | 17 | [FieldOffset(9)] private Type ValueType; 18 | 19 | public static StackValue Null() 20 | { 21 | return new StackValue 22 | { 23 | Width = BitWidth.Width8, 24 | LValue = 0, 25 | ValueType = Type.Null 26 | }; 27 | } 28 | 29 | public static StackValue Value(float value) 30 | { 31 | return new StackValue 32 | { 33 | Width = BitWidth.Width32, 34 | DValue = value, 35 | ValueType = Type.Float 36 | }; 37 | } 38 | 39 | public static StackValue Value(double value) 40 | { 41 | return new StackValue 42 | { 43 | Width = BitWidthUtil.Width(value), 44 | DValue = value, 45 | ValueType = Type.Float 46 | }; 47 | } 48 | 49 | public static StackValue Value(bool value) 50 | { 51 | return new StackValue 52 | { 53 | Width = BitWidth.Width8, 54 | LValue = value ? 1 : 0, 55 | ValueType = Type.Bool 56 | }; 57 | } 58 | 59 | public static StackValue Value(long value) 60 | { 61 | return new StackValue 62 | { 63 | Width = BitWidthUtil.Width(value), 64 | LValue = value, 65 | ValueType = Type.Int 66 | }; 67 | } 68 | 69 | public static StackValue Value(ulong value) 70 | { 71 | return new StackValue 72 | { 73 | Width = BitWidthUtil.Width(value), 74 | UValue = value, 75 | ValueType = Type.Uint 76 | }; 77 | } 78 | 79 | public static StackValue Value(ulong value, BitWidth width, Type type) 80 | { 81 | return new StackValue 82 | { 83 | Width = width, 84 | UValue = value, 85 | ValueType = type 86 | }; 87 | } 88 | 89 | public static StackValue Value(long value, BitWidth width, Type type) 90 | { 91 | return new StackValue 92 | { 93 | Width = width, 94 | LValue = value, 95 | ValueType = type 96 | }; 97 | } 98 | 99 | public BitWidth StoredWidth(BitWidth bitWidth = BitWidth.Width8) 100 | { 101 | if (TypesUtil.IsInline(ValueType)) 102 | { 103 | return (BitWidth) Math.Max((int) bitWidth, (int) Width); 104 | } 105 | 106 | return Width; 107 | } 108 | 109 | public byte StoredPackedType(BitWidth bitWidth = BitWidth.Width8) 110 | { 111 | return TypesUtil.PackedType(ValueType, StoredWidth(bitWidth)); 112 | } 113 | 114 | public BitWidth ElementWidth(ulong size, int index) 115 | { 116 | if (TypesUtil.IsInline(ValueType)) 117 | { 118 | return Width; 119 | } 120 | 121 | for (var i = 0; i < 4; i++) 122 | { 123 | var width = (ulong)1 << i; 124 | var offsetLoc = size + BitWidthUtil.PaddingSize(size, width) + (ulong)index * width; 125 | var offset = offsetLoc - UValue; 126 | var bitWidth = BitWidthUtil.Width(offset); 127 | if ((1UL << (byte) bitWidth) == width) 128 | { 129 | return bitWidth; 130 | } 131 | } 132 | throw new Exception($"Element with size: {size} and index: {index} is of unknown width"); 133 | } 134 | 135 | public long AsLong => LValue; 136 | public ulong AsULong => UValue; 137 | public double AsDouble => DValue; 138 | 139 | public bool IsFloat32 => ValueType == Type.Float && Width == BitWidth.Width32; 140 | public bool IsOffset => TypesUtil.IsInline(ValueType) == false; 141 | 142 | public Type TypeOfValue => ValueType; 143 | public BitWidth InternalWidth => Width; 144 | } 145 | } -------------------------------------------------------------------------------- /Runtime/StackValue.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bc8110ed96d07463fad90102a2aaa76a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Types.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FlexBuffers 4 | { 5 | public enum Type: byte 6 | { 7 | Null, Int, Uint, Float, 8 | Key, String, IndirectInt, IndirectUInt, IndirectFloat, 9 | Map, Vector, VectorInt, VectorUInt, VectorFloat, VectorKey, VectorString, 10 | VectorInt2, VectorUInt2, VectorFloat2, 11 | VectorInt3, VectorUInt3, VectorFloat3, 12 | VectorInt4, VectorUInt4, VectorFloat4, 13 | Blob, Bool, VectorBool = 36 14 | } 15 | 16 | public static class TypesUtil 17 | { 18 | public static bool IsInline(Type type) 19 | { 20 | return type == Type.Bool || (byte) type <= (byte) Type.Float; 21 | } 22 | 23 | public static bool IsTypedVectorElement(Type type) 24 | { 25 | var typeValue = (byte) type; 26 | return type == Type.Bool || (typeValue >= (byte) Type.Int && typeValue <= (byte) Type.String); 27 | } 28 | 29 | public static bool IsTypedVector(Type type) 30 | { 31 | var typeValue = (byte) type; 32 | return type == Type.VectorBool || (typeValue >= (byte) Type.VectorInt && typeValue <= (byte) Type.VectorString); 33 | } 34 | 35 | public static bool IsFixedTypedVector(Type type) 36 | { 37 | var typeValue = (byte) type; 38 | return (typeValue >= (byte) Type.VectorInt2 && typeValue <= (byte) Type.VectorFloat4); 39 | } 40 | 41 | public static bool IsAVector(Type type) 42 | { 43 | return IsTypedVector(type) || IsFixedTypedVector(type) || type == Type.Vector; 44 | } 45 | 46 | public static Type ToTypedVector(Type type, byte length) 47 | { 48 | var typeValue = (byte) type; 49 | if (length == 0) 50 | { 51 | return (Type) (typeValue - (byte) Type.Int + (byte) Type.VectorInt); 52 | } 53 | if (length == 2) 54 | { 55 | return (Type) (typeValue - (byte) Type.Int + (byte) Type.VectorInt2); 56 | } 57 | if (length == 3) 58 | { 59 | return (Type) (typeValue - (byte) Type.Int + (byte) Type.VectorInt3); 60 | } 61 | if (length == 4) 62 | { 63 | return (Type) (typeValue - (byte) Type.Int + (byte) Type.VectorInt4); 64 | } 65 | throw new Exception($"Unexpected length: {length}"); 66 | } 67 | 68 | public static Type TypedVectorElementType(Type type) 69 | { 70 | var typeValue = (byte) type; 71 | return (Type) (typeValue - (byte) Type.VectorInt + (byte) Type.Int); 72 | } 73 | 74 | public static Type FixedTypedVectorElementType(Type type) 75 | { 76 | var fixedType = (byte) type - (byte) Type.VectorInt2; 77 | return (Type)(fixedType % 3 + (int) Type.Int); 78 | } 79 | 80 | public static int FixedTypedVectorElementSize(Type type) 81 | { 82 | var fixedType = (byte) type - (byte) Type.VectorInt2; 83 | return fixedType / 3 + 2; 84 | } 85 | 86 | public static byte PackedType(Type type, BitWidth bitWidth) 87 | { 88 | return (byte) ((byte) bitWidth | ((byte)type << 2)); 89 | } 90 | 91 | public static byte NullPackedType() 92 | { 93 | return PackedType(Type.Null, BitWidth.Width8); 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /Runtime/Types.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2b0bfcf95e8d946578df4e48648f5bed 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Unity.FlexBuffers.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "FlexBuffers", 3 | "references": [], 4 | "includePlatforms": [], 5 | "excludePlatforms": [], 6 | "allowUnsafeCode": false, 7 | "overrideReferences": false, 8 | "precompiledReferences": [], 9 | "autoReferenced": true, 10 | "defineConstraints": [], 11 | "versionDefines": [], 12 | "noEngineReferences": false 13 | } -------------------------------------------------------------------------------- /Runtime/Unity.FlexBuffers.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1a82e366e6a014b228ca1f953480ed08 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/XmlToFlexBufferConverter.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Xml; 3 | 4 | namespace FlexBuffers 5 | { 6 | public static class XmlToFlexBufferConverter 7 | { 8 | public static byte[] Convert(string xmlData) 9 | { 10 | XmlDocument doc = new XmlDocument(); 11 | 12 | doc.Load(new StringReader(xmlData)); 13 | 14 | var flx = new FlexBuffer(); 15 | 16 | Process(flx, doc.DocumentElement); 17 | 18 | return flx.Finish(); 19 | } 20 | 21 | private static void Process(FlexBuffer flx, XmlNode element) 22 | { 23 | var node = flx.StartVector(); 24 | flx.AddKey("tagName"); 25 | flx.Add(element.Name); 26 | var attributes = element.Attributes; 27 | if (attributes != null) 28 | { 29 | for (var i = 0; i < attributes.Count; i++) 30 | { 31 | var att = attributes.Item(i); 32 | flx.AddKey(att.Name); 33 | flx.Add(att.Value); 34 | } 35 | } 36 | 37 | var children = element.ChildNodes; 38 | if (children.Count > 0) 39 | { 40 | flx.AddKey("children"); 41 | var childVector = flx.StartVector(); 42 | for (var i = 0; i < children.Count; i++) 43 | { 44 | var child = children[i]; 45 | if (child.NodeType == XmlNodeType.Text || child.NodeType == XmlNodeType.CDATA) 46 | { 47 | flx.Add(child.Value); 48 | } else if (child.NodeType == XmlNodeType.Comment) 49 | { 50 | 51 | } else 52 | { 53 | Process(flx, child); 54 | } 55 | } 56 | 57 | flx.EndVector(childVector, false, false); 58 | } 59 | flx.SortAndEndMap(node); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /Runtime/XmlToFlexBufferConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 65ee9865279f24c4aa1783ec60f4d55b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.mzaks.flexbuffers", 3 | "version": "0.1.8", 4 | "displayName": "FlexBuffers Support", 5 | "description": "This package brings FlexBuffers support to your Unity project", 6 | "keywords": [ 7 | "FlexBuffer", 8 | "binary" 9 | ], 10 | "author": { 11 | "name": "Maxim Zaks", 12 | "email": "maxim.zaks@gmail.com", 13 | "url": "https://github.com/mzaks" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c46b9f8b4e69744698b73d6c6d07a2aa 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------