/// <summary> /// Constructor. /// </summary> /// <param name="paths">Paths.</param> /// <param name="jumps">Jumps.</param> /// <param name="fieldTypes">Field types.</param> private BinaryStructure(BinaryStructureEntry[][] paths, BinaryStructureJumpTable[] jumps, IDictionary<string, byte> fieldTypes) { _paths = paths; _jumps = jumps; _fieldTypes = fieldTypes; }
/// <summary> /// Gets field ID if possible. /// </summary> /// <param name="fieldName">Field name.</param> /// <param name="fieldType">Field type.</param> /// <param name="pathIdx">Path index, changes during jumps.</param> /// <param name="actionIdx">Action index.</param> /// <returns>Field ID or zero in case there are no matching path.</returns> public int GetFieldId(string fieldName, byte fieldType, ref int pathIdx, int actionIdx) { Debug.Assert(fieldName != null); Debug.Assert(pathIdx <= _paths.Length); // Get path. BinaryStructureEntry[] path = _paths[pathIdx]; if (actionIdx < path.Length) { // Get entry matching the action index. BinaryStructureEntry entry = path[actionIdx]; if (entry.IsExpected(fieldName, fieldType)) { // Entry matches our expectations, return. return(entry.Id); } if (entry.IsJumpTable) { // Entry is a pointer to a jump table. Debug.Assert(entry.Id < _jumps.Length); BinaryStructureJumpTable jmpTbl = _jumps[entry.Id]; if (jmpTbl == null) { return(0); } int pathIdx0 = jmpTbl.GetPathIndex(fieldName); if (pathIdx0 < 0) { return(0); } Debug.Assert(pathIdx0 < _paths.Length); entry = _paths[pathIdx0][actionIdx]; entry.ValidateType(fieldType); pathIdx = pathIdx0; return(entry.Id); } } // Failed to find anything because this is a new field. return(0); }
/// <summary> /// Copy and possibly expand jump tables. /// </summary> /// <param name="additionalJumps">Amount of additional jumps required.</param> /// <returns>Result.</returns> private BinaryStructureJumpTable[] CopyJumps(int additionalJumps) { var newJumps = new BinaryStructureJumpTable[_jumps.Length + additionalJumps]; // The very first jump is always null so that we can distinguish between jump table // and empty value in BinaryStructureEntry. for (int i = 1; i < _jumps.Length; i++) { newJumps[i] = _jumps[i].Copy(); } return(newJumps); }
/// <summary> /// Merge updates into a new type structure. /// </summary> /// <param name="pathIdx">Path index.</param> /// <param name="updates">Updates.</param> /// <returns>New type structure with updates.</returns> public BinaryStructure Merge(int pathIdx, IList <BinaryStructureUpdate> updates) { if (updates == null || updates.Count == 0) { return(this); } // Algorithm ensures that updates are applied to the same type structure, // where they were initially observed. This allow us to keep structure // internals simpler and more efficient. On the other hand, this imposes // some performance hit because in case of concurrent update, recorded // changes will be discarded and recorded again during the next write // on the same path. This should occur only during application warmup. // Note that field types are merged anyway to avoid metadata clashes. BinaryStructure res = MergeFieldTypes(updates); BinaryStructureUpdate firstUpdate = updates[0]; // Get entry where updates should start. BinaryStructureEntry[] path = _paths[pathIdx]; var startEntry = firstUpdate.Index < path.Length ? path[firstUpdate.Index] : default(BinaryStructureEntry); if (startEntry.IsEmpty) { // We are on the empty/non-existent entry. Continue the path without branching. var newPaths = CopyPaths(firstUpdate.Index + updates.Count, 0); ApplyUpdatesToPath(newPaths[pathIdx], updates); res = new BinaryStructure(newPaths, _jumps, res._fieldTypes); } else if (startEntry.IsJumpTable) { // We are on the jump table. Add a new path and record it in the jump table. // 1. Prepare new structures. var newPaths = CopyPaths(firstUpdate.Index + updates.Count, 1); var newJumps = CopyJumps(0); // New path will be the last one. int newPathIdx = newPaths.Length - 1; // Apply updates to the new path. ApplyUpdatesToPath(newPaths[newPathIdx], updates); // Add the jump to the table. newJumps[startEntry.Id] = newJumps[startEntry.Id].CopyAndAdd(firstUpdate.FieldName, newPathIdx); res = new BinaryStructure(newPaths, newJumps, res._fieldTypes); } else { // We are on existing entry. Need to create a new jump table here and two new paths. // 1. Prepare new structures. var newPaths = CopyPaths(firstUpdate.Index + updates.Count, 2); var newJumps = CopyJumps(1); // Old path will be moved here. int oldPathIdx = newPaths.Length - 2; // New path will reside here. int newPathIdx = newPaths.Length - 1; // Create new jump table. int newJumpIdx = newJumps.Length - 1; newJumps[newJumpIdx] = new BinaryStructureJumpTable(startEntry.Name, oldPathIdx, firstUpdate.FieldName, newPathIdx); // Re-create old path in two steps: move old path to the new place, then clean the old path. for (int i = firstUpdate.Index; i < path.Length; i++) { newPaths[oldPathIdx][i] = newPaths[pathIdx][i]; if (i == firstUpdate.Index) { // Inject jump table ... newPaths[pathIdx][i] = new BinaryStructureEntry(newJumpIdx); } else { // ... or just reset. newPaths[pathIdx][i] = new BinaryStructureEntry(); } } // Apply updates to the new path. ApplyUpdatesToPath(newPaths[newPaths.Length - 1], updates); res = new BinaryStructure(newPaths, newJumps, res._fieldTypes); } return(res); }
/// <summary> /// Copy and possibly expand jump tables. /// </summary> /// <param name="additionalJumps">Amount of additional jumps required.</param> /// <returns>Result.</returns> private BinaryStructureJumpTable[] CopyJumps(int additionalJumps) { var newJumps = new BinaryStructureJumpTable[_jumps.Length + additionalJumps]; // The very first jump is always null so that we can distinguish between jump table // and empty value in BinaryStructureEntry. for (int i = 1; i < _jumps.Length; i++) newJumps[i] = _jumps[i].Copy(); return newJumps; }
/// <summary> /// Merge updates into a new type structure. /// </summary> /// <param name="exp">Expected type structure to apply updates to </param> /// <param name="pathIdx">Path index.</param> /// <param name="updates">Updates.</param> /// <returns>New type structure with updates.</returns> public BinaryStructure Merge(BinaryStructure exp, int pathIdx, IList<BinaryStructureUpdate> updates) { if (updates.Count == 0) return this; // Algorithm ensures that updates are applied to the same type structure, // where they were initially observed. This allow us to keep structure // internals simpler and more efficient. On the other hand, this imposes // some performance hit because in case of concurrent update, recorded // changes will be discarded and recorded again during the next write // on the same path. This should occur only during application warmup. // Note that field types are merged anyway to avoid metadata clashes. BinaryStructure res = MergeFieldTypes(updates); if (ReferenceEquals(exp, this)) { BinaryStructureUpdate firstUpdate = updates[0]; if (firstUpdate.Index == 0) { // Special case: the very first structure update. Simply attach all updates. Debug.Assert(_paths.Length == 1); Debug.Assert(_paths[0].Length == 0); Debug.Assert(pathIdx == 0); var newPaths = CopyPaths(updates.Count, 0); ApplyUpdatesToPath(newPaths[0], updates); res = new BinaryStructure(newPaths, _jumps, res._fieldTypes); } else { // Get entry where updates should start. BinaryStructureEntry[] path = _paths[pathIdx]; BinaryStructureEntry startEntry = default(BinaryStructureEntry); if (firstUpdate.Index < path.Length) startEntry = path[firstUpdate.Index]; if (startEntry.IsEmpty) { // We are on the empty/non-existent entry. Continue the path without branching. var newPaths = CopyPaths(firstUpdate.Index + updates.Count, 0); ApplyUpdatesToPath(newPaths[pathIdx], updates); res = new BinaryStructure(newPaths, _jumps, res._fieldTypes); } else if (startEntry.IsJumpTable) { // We are on the jump table. Add a new path and record it in the jump table. // 1. Prepare new structures. var newPaths = CopyPaths(firstUpdate.Index + updates.Count, 1); var newJumps = CopyJumps(0); // New path will be the last one. int newPathIdx = newPaths.Length - 1; // Apply updates to the new path. ApplyUpdatesToPath(newPaths[newPathIdx], updates); // Add the jump to the table. newJumps[startEntry.Id] = newJumps[startEntry.Id].CopyAndAdd(firstUpdate.FieldName, newPathIdx); res = new BinaryStructure(newPaths, newJumps, res._fieldTypes); } else { // We are on existing entry. Need to create a new jump table here and two new paths. // 1. Prepaare new structures. var newPaths = CopyPaths(firstUpdate.Index + updates.Count, 2); var newJumps = CopyJumps(1); // Old path will be moved here. int oldPathIdx = newPaths.Length - 2; // New path will reside here. int newPathIdx = newPaths.Length - 1; // Create new jump table. int newJumpIdx = newJumps.Length - 1; newJumps[newJumpIdx] = new BinaryStructureJumpTable(startEntry.Name, oldPathIdx, firstUpdate.FieldName, newPathIdx); // Re-create old path in two steps: move old path to the new place, then clean the old path. for (int i = firstUpdate.Index; i < path.Length; i++) { newPaths[oldPathIdx][i] = newPaths[pathIdx][i]; if (i == firstUpdate.Index) // Inject jump table ... newPaths[pathIdx][i] = new BinaryStructureEntry(newJumpIdx); else // ... or just reset. newPaths[pathIdx][i] = new BinaryStructureEntry(); } // Apply updates to the new path. ApplyUpdatesToPath(newPaths[newPaths.Length - 1], updates); res = new BinaryStructure(newPaths, newJumps, res._fieldTypes); } } } return res; }