-
-
Notifications
You must be signed in to change notification settings - Fork 141
ByteProvider
ByteProvider (in WpfHexEditor.Core) is the virtual-view data layer of the HexEditor. It presents a mutable logical view of a file while keeping the physical file unchanged until Save().
flowchart TD
User["User edits"]
VM["HexEditorViewModel\nvirtual byte view"]
BP["ByteProvider"]
PM["PositionMapper\nO(log n) segment tree"]
File["Physical file\nunchanged until Save()"]
subgraph Edits["Edit tracking"]
Mods["Modifications\nDictionary<long, byte>\nin-place byte changes"]
Ins["Insertions\nordered segments (LIFO)\nbyte[] + virtual offset"]
Del["Deletions\nDictionary<long, int>\nvirtual offset + count"]
end
subgraph Commands["Undo / Redo stack"]
ModCmd["ModifyCommand"]
InsCmd["InsertCommand"]
DelCmd["DeleteCommand"]
Batch["BatchCommand\n(group as one undo step)"]
end
User --> VM
VM --> BP
BP --> PM
BP --> Edits
PM --> File
BP --> Commands
The user always sees a logical (virtual) byte stream that includes all pending edits. The physical file is never modified until an explicit Save().
Physical file: [AA BB CC DD EE FF] (6 bytes, unchanged)
Modifications: { 2 → 0x99 } (byte at pos 2 changed)
Insertions: { after 3: [11 22] } (2 bytes inserted)
Virtual view: [AA BB 99 DD 11 22 EE FF] (8 bytes seen by editor)
PositionMapper maintains a segment tree to translate between physical and virtual positions in O(log n):
// Physical file offset → virtual editor offset
long virtualPos = positionMapper.PhysicalToVirtual(physicalOffset);
// Virtual editor offset → physical file offset
long physicalPos = positionMapper.VirtualToPhysical(virtualOffset);This is rebuilt incrementally as edits accumulate.
ByteProvider bp = new ByteProvider(filePath);
// --- Read ---
byte value = bp.GetByte(virtualOffset);
byte[] block = bp.GetBytes(virtualOffset, count);
long length = bp.Length; // virtual length (includes insertions)
long physLen = bp.FileLength; // physical file length
// --- Modify (in-place byte change) ---
bp.AddByteModified(newValue, virtualOffset);
// --- Insert ---
bp.AddByteInserted(newByte, virtualOffset);
bp.AddBytesInserted(newBytes, virtualOffset);
// --- Delete ---
bp.AddByteDeleted(virtualOffset);
bp.AddBytesDeleted(virtualOffset, count);
// --- Undo / Redo ---
bp.Undo();
bp.Redo();
bool canUndo = bp.CanUndo;
bool canRedo = bp.CanRedo;
// --- Save ---
await bp.SaveAsync(); // smart save (see below)
await bp.SaveAsAsync(newFilePath);
// --- State ---
bool isDirty = bp.IsModified;
bool hasInserts = bp.HasInsertedBytes;
bool hasDels = bp.HasDeletedBytes;
// --- Events ---
bp.DataChanged += (s, e) => { /* refresh viewport */ };
bp.LengthChanged += (s, e) => { /* update scrollbar */ };
bp.Undone += (s, e) => { };
bp.Redone += (s, e) => { };Save() chooses the fastest safe write path:
flowchart TD
Save["Save()"]
Check{"Has insertions\nor deletions?"}
Fast["Fast path\nIn-place overwrite\nonly modified bytes\n(FileStream seek+write)"]
Full["Full rebuild\n1. Write to temp file\n2. File.Replace()\n(atomic swap)"]
Save --> Check
Check -->|No — modifications only| Fast
Check -->|Yes| Full
- Fast path: 10–100× faster for pure byte-modification workloads (no length change)
-
Full rebuild:
File.Replace()is atomic — no data loss on crash mid-save
Every edit is a command pushed onto the undo stack:
| Command | Triggered by |
|---|---|
ModifyCommand |
AddByteModified() |
InsertCommand |
AddByteInserted() / AddBytesInserted()
|
DeleteCommand |
AddByteDeleted() / AddBytesDeleted()
|
BatchCommand |
Groups multiple commands into one undo step |
// Group a paste operation as a single undo step
bp.BeginBatch();
foreach (var (offset, value) in clipboard)
bp.AddByteModified(value, offset);
bp.EndBatch(); // entire paste undone in one Ctrl+Z| Variant | Backed by | Use case |
|---|---|---|
ByteProvider(string filePath) |
Physical file | Normal file editing |
ByteProvider(Stream stream) |
Stream | In-memory or network streams |
ByteProvider(byte[] data) |
Byte array | Unit tests, small buffers |
ReadOnlyByteProvider |
Any | Locked / read-only view |
- Modifications dictionary: O(m) where m = number of changed bytes
- Insertions: O(i) where i = number of inserted segments
- PositionMapper segment tree: O(i + d) nodes for i insertions + d deletions
- File content is never loaded into memory — ByteProvider reads from
FileStreamon demand usingSpan<T>/ArrayPool<T>for zero-alloc reads
For very large files (10 GB+), the memory footprint of ByteProvider is proportional only to the number of edits, not the file size.
- Architecture — ByteProvider in the full stack
- Rendering Engine — how the viewport reads from ByteProvider
-
Search Architecture —
FindReplaceServicereads via ByteProvider - API Reference — full HexEditor public API
✨ Wpf HexEditor user control, by Derek Tremblay (derektremblay666@gmail.com) coded for your fun! 😊🤟
- API Reference
- Performance
- Services
- Core Components
- ByteProvider
- Rendering Engine
- Search Architecture
- WpfHexEditor.Shell (renamed from Docking.Wpf)
- Code Editor ~90% (NavBar, Inline Hints, Quick Info)
- XAML Visual Designer ~70%
- Shared UndoEngine (coalescing + transactions)
- VS
.sln/.csproj+ Open-Folder mode - Build System (IBuildAdapter + MSBuild plugin)
- Assembly Explorer + NuGet Solution Manager
- Synalysis Grammar Support (IGrammarProvider)