Improving the buffer and bug fixing
This commit is contained in:
@@ -6,7 +6,10 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#define Assert(x) assert(x)
|
||||
#define Assert(x) \
|
||||
if (!(x)) { \
|
||||
__debugbreak(); \
|
||||
}
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
#define OS_MAC 1
|
||||
@@ -969,8 +972,8 @@ inline TempArena GetScratch(Arena *c1 = NULL, Arena *c2 = NULL) {
|
||||
struct Scratch {
|
||||
TempArena checkpoint;
|
||||
Scratch() { this->checkpoint = GetScratch(); }
|
||||
Scratch(TempArena conflict) { this->checkpoint = GetScratch(conflict.arena); }
|
||||
Scratch(TempArena c1, TempArena c2) { this->checkpoint = GetScratch(c1.arena, c2.arena); }
|
||||
Scratch(Arena *conflict) { this->checkpoint = GetScratch(conflict); }
|
||||
Scratch(Arena *c1, Arena *c2) { this->checkpoint = GetScratch(c1, c2); }
|
||||
~Scratch() { EndTemp(checkpoint); }
|
||||
operator Arena *() { return checkpoint.arena; }
|
||||
operator Allocator() { return *checkpoint.arena; }
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
|
||||
/*
|
||||
Ranges are from 0 to n+1 (one past last index).
|
||||
<0,4> = 0,1,2,3
|
||||
|
||||
These seem the best, I tried other formations but these are
|
||||
much better then the rest.
|
||||
|
||||
First of all you can represent the cursor at the end of the buffer
|
||||
by doing: <buffer_len, buffer_len>. This action in itself doesn't
|
||||
select anything, the formation doesn't force you to index the
|
||||
buffer and so on, it's reduced to a pure position.
|
||||
|
||||
This property of being able to represent pure positions makes
|
||||
it possible to clamp the values to the same range <0, buffer_len>,
|
||||
and if the things are past the range we end up with a pure value.
|
||||
Very nice behaviour, The program won't delete anything.
|
||||
|
||||
*/
|
||||
struct Range {
|
||||
int64_t min;
|
||||
int64_t max; // one past last index
|
||||
@@ -10,12 +29,12 @@ struct Edit {
|
||||
};
|
||||
|
||||
struct Buffer {
|
||||
Allocator allocator;
|
||||
char *data[2];
|
||||
int64_t cap;
|
||||
int64_t len;
|
||||
int bi; // current buffer index
|
||||
Array<String> lines;
|
||||
Allocator allocator;
|
||||
char *data[2];
|
||||
int64_t cap;
|
||||
int64_t len;
|
||||
int bi; // current buffer index
|
||||
Array<Range> lines;
|
||||
};
|
||||
|
||||
int64_t GetRangeSize(Range range) {
|
||||
@@ -28,132 +47,22 @@ Range GetRange(const Buffer &buffer) {
|
||||
return result;
|
||||
}
|
||||
|
||||
int64_t ClampMax(Buffer *buffer, int64_t pos) {
|
||||
int64_t Clamp(Buffer *buffer, int64_t pos) {
|
||||
int64_t result = Clamp(pos, (int64_t)0, buffer->len);
|
||||
return result;
|
||||
}
|
||||
int64_t ClampMin(Buffer *buffer, int64_t pos) {
|
||||
int64_t end_of_buffer = Max((int64_t)0, buffer->len - 1);
|
||||
int64_t result = Clamp(pos, (int64_t)0, end_of_buffer);
|
||||
|
||||
Range Clamp(Buffer *buffer, Range range) {
|
||||
Range result = {};
|
||||
result.min = Clamp(buffer, range.min);
|
||||
result.max = Clamp(buffer, range.max);
|
||||
return result;
|
||||
}
|
||||
|
||||
void ApplyEdits(Buffer *buffer, Array<Edit> edits) {
|
||||
int64_t size_to_delete = 0;
|
||||
int64_t size_to_insert = 0;
|
||||
For(edits) {
|
||||
it.range.min = ClampMin(buffer, it.range.min);
|
||||
it.range.max = ClampMax(buffer, it.range.max);
|
||||
size_to_delete += GetRangeSize(it.range);
|
||||
size_to_insert += it.string.len;
|
||||
}
|
||||
|
||||
#if DEBUG_BUILD
|
||||
ForItem(it1, edits) {
|
||||
ForItem(it2, edits) {
|
||||
if (&it1 == &it2) continue;
|
||||
|
||||
bool a2_inside = it2.range.min >= it1.range.min && it2.range.min < it1.range.max;
|
||||
Assert(!a2_inside);
|
||||
|
||||
bool b2_inside = it2.range.max > it1.range.min && it2.range.max <= it1.range.max;
|
||||
Assert(!b2_inside);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int64_t len_offset = size_to_insert - size_to_delete;
|
||||
int64_t allocated_size_required = Max((int64_t)0, len_offset);
|
||||
if (buffer->len + allocated_size_required > buffer->cap) {
|
||||
int64_t new_cap = AlignUp(buffer->cap + allocated_size_required, 4096);
|
||||
if (buffer->allocator.proc == NULL) buffer->allocator = GetSystemAllocator();
|
||||
|
||||
{
|
||||
char *data = AllocArray(buffer->allocator, char, new_cap);
|
||||
Assert(data);
|
||||
memcpy(data, buffer->data[0], buffer->len);
|
||||
Dealloc(buffer->allocator, &buffer->data[0]);
|
||||
buffer->data[0] = data;
|
||||
}
|
||||
{
|
||||
char *data = AllocArray(buffer->allocator, char, new_cap);
|
||||
Assert(data);
|
||||
memcpy(data, buffer->data[1], buffer->len);
|
||||
Dealloc(buffer->allocator, &buffer->data[1]);
|
||||
buffer->data[1] = data;
|
||||
}
|
||||
buffer->cap = new_cap;
|
||||
}
|
||||
|
||||
Scratch scratch;
|
||||
Array<Edit> writes = {scratch};
|
||||
int64_t prev_source = 0;
|
||||
int64_t prev_dest = 0;
|
||||
|
||||
For(edits) {
|
||||
Range source_range = {prev_source, it.range.min};
|
||||
if (GetRangeSize(source_range) != 0) {
|
||||
String source_string = {};
|
||||
source_string.data = buffer->data[buffer->bi] + source_range.min;
|
||||
source_string.len = GetRangeSize(source_range);
|
||||
Range dest_range = {prev_dest, prev_dest + source_string.len};
|
||||
writes.add({dest_range, source_string});
|
||||
|
||||
prev_dest = dest_range.max;
|
||||
}
|
||||
|
||||
Range dest_range = {prev_dest, prev_dest + it.string.len};
|
||||
writes.add({dest_range, it.string});
|
||||
prev_dest = dest_range.max;
|
||||
prev_source = it.range.max;
|
||||
}
|
||||
|
||||
// Add remaining range
|
||||
Range source_range = {prev_source, buffer->len};
|
||||
if (GetRangeSize(source_range)) {
|
||||
String source_string = {};
|
||||
source_string.data = buffer->data[buffer->bi] + source_range.min;
|
||||
source_string.len = GetRangeSize(source_range);
|
||||
Range dest_range = {prev_dest, prev_dest + source_string.len};
|
||||
writes.add({dest_range, source_string});
|
||||
}
|
||||
|
||||
#if DEBUG_BUILD
|
||||
for (int64_t i = 0; i < writes.len - 1; i += 1) {
|
||||
Assert(writes[i].range.max == writes[i + 1].range.min);
|
||||
}
|
||||
#endif
|
||||
|
||||
int64_t new_buffer_len = 0;
|
||||
int dsti = (buffer->bi + 1) % 2;
|
||||
For(writes) {
|
||||
memcpy(buffer->data[dsti] + new_buffer_len, it.string.data, it.string.len);
|
||||
new_buffer_len += it.string.len;
|
||||
}
|
||||
buffer->bi = dsti;
|
||||
Assert(new_buffer_len == buffer->len + len_offset);
|
||||
buffer->len = new_buffer_len;
|
||||
|
||||
String string = {buffer->data[buffer->bi], buffer->len};
|
||||
buffer->lines = Split(buffer->allocator, string, "\n");
|
||||
}
|
||||
|
||||
void AddEdit(Array<Edit> *edits, Range range, String string) {
|
||||
edits->add({range, string});
|
||||
}
|
||||
|
||||
int64_t AdjustUTF8Pos(Buffer *buffer, int64_t pos, int64_t direction = 1) {
|
||||
int64_t result = pos;
|
||||
for (; result >= 0 && result < buffer->len;) {
|
||||
if (IsUTF8ContinuationByte(buffer->data[buffer->bi][0])) {
|
||||
result += direction;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool InBounds(Buffer *buffer, int64_t pos) {
|
||||
bool result = pos >= 0 && pos < buffer->len;
|
||||
return result;
|
||||
@@ -169,12 +78,140 @@ char *GetCharP(Buffer *buffer, int64_t pos) {
|
||||
return buffer->data[buffer->bi] + pos;
|
||||
}
|
||||
|
||||
String GetString(Buffer *buffer, Range range) {
|
||||
range = Clamp(buffer, range);
|
||||
String result = {GetCharP(buffer, range.min), GetRangeSize(range)};
|
||||
return result;
|
||||
}
|
||||
|
||||
void ApplyEdits(Buffer *buffer, Array<Edit> edits) {
|
||||
int64_t size_to_delete = 0;
|
||||
int64_t size_to_insert = 0;
|
||||
For(edits) {
|
||||
it.range.min = Clamp(buffer, it.range.min);
|
||||
it.range.max = Clamp(buffer, it.range.max);
|
||||
size_to_delete += GetRangeSize(it.range);
|
||||
size_to_insert += it.string.len;
|
||||
}
|
||||
|
||||
#if DEBUG_BUILD
|
||||
ForItem(it1, edits) {
|
||||
ForItem(it2, edits) {
|
||||
if (&it1 == &it2) continue;
|
||||
|
||||
bool a2_inside = it2.range.min >= it1.range.min && it2.range.min <= it1.range.max;
|
||||
Assert(!a2_inside);
|
||||
|
||||
bool b2_inside = it2.range.max >= it1.range.min && it2.range.max <= it1.range.max;
|
||||
Assert(!b2_inside);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int64_t len_offset = size_to_insert - size_to_delete;
|
||||
int64_t allocated_size_required = Max((int64_t)0, len_offset);
|
||||
if (buffer->len + allocated_size_required > buffer->cap) {
|
||||
int64_t new_cap = AlignUp(buffer->cap + allocated_size_required, 4096);
|
||||
if (buffer->allocator.proc == NULL) buffer->allocator = GetSystemAllocator();
|
||||
|
||||
for (int i = 0; i < 2; i += 1) {
|
||||
char *data = AllocArray(buffer->allocator, char, new_cap);
|
||||
Assert(data);
|
||||
memcpy(data, buffer->data[i], buffer->len);
|
||||
Dealloc(buffer->allocator, &buffer->data[i]);
|
||||
buffer->data[i] = data;
|
||||
}
|
||||
buffer->cap = new_cap;
|
||||
}
|
||||
|
||||
int srci = buffer->bi;
|
||||
int dsti = (buffer->bi + 1) % 2;
|
||||
|
||||
Scratch scratch((Arena *)buffer->allocator.object);
|
||||
Array<Edit> writes = {scratch};
|
||||
int64_t prev_source = 0;
|
||||
int64_t prev_dest = 0;
|
||||
|
||||
For(edits) {
|
||||
Range source_range = {prev_source, it.range.min};
|
||||
if (GetRangeSize(source_range) != 0) {
|
||||
String source_string = {};
|
||||
source_string.data = buffer->data[srci] + source_range.min;
|
||||
source_string.len = GetRangeSize(source_range);
|
||||
Range dest_range = {prev_dest, prev_dest + source_string.len};
|
||||
writes.add({dest_range, source_string});
|
||||
|
||||
prev_dest = dest_range.max;
|
||||
}
|
||||
|
||||
Range dest_range = {prev_dest, prev_dest + it.string.len};
|
||||
writes.add({dest_range, it.string});
|
||||
prev_dest = dest_range.max;
|
||||
prev_source = it.range.max;
|
||||
}
|
||||
|
||||
// Add remaining range
|
||||
Range source_range = {prev_source, buffer->len};
|
||||
if (GetRangeSize(source_range)) {
|
||||
String source_string = {};
|
||||
source_string.data = buffer->data[srci] + source_range.min;
|
||||
source_string.len = GetRangeSize(source_range);
|
||||
Range dest_range = {prev_dest, prev_dest + source_string.len};
|
||||
writes.add({dest_range, source_string});
|
||||
}
|
||||
|
||||
#if DEBUG_BUILD
|
||||
for (int64_t i = 0; i < writes.len - 1; i += 1) {
|
||||
Assert(writes[i].range.max == writes[i + 1].range.min);
|
||||
}
|
||||
#endif
|
||||
|
||||
int64_t new_buffer_len = 0;
|
||||
For(writes) {
|
||||
memcpy(buffer->data[dsti] + new_buffer_len, it.string.data, it.string.len);
|
||||
new_buffer_len += it.string.len;
|
||||
}
|
||||
buffer->bi = dsti;
|
||||
Assert(new_buffer_len == buffer->len + len_offset);
|
||||
buffer->len = new_buffer_len;
|
||||
|
||||
// Update lines
|
||||
{
|
||||
String delimiter = "\n";
|
||||
String string = {buffer->data[dsti], buffer->len};
|
||||
buffer->lines.allocator = buffer->allocator;
|
||||
buffer->lines.clear();
|
||||
|
||||
int64_t index = 0;
|
||||
int64_t base_index = 0;
|
||||
while (Seek(string, delimiter, &index)) {
|
||||
buffer->lines.add({base_index, base_index + index});
|
||||
base_index += index + delimiter.len;
|
||||
string = string.skip(index + delimiter.len);
|
||||
}
|
||||
buffer->lines.add({base_index, base_index + string.len});
|
||||
}
|
||||
}
|
||||
|
||||
int64_t AdjustUTF8Pos(Buffer *buffer, int64_t pos, int64_t direction = 1) {
|
||||
int64_t result = pos;
|
||||
for (; result >= 0 && result < buffer->len;) {
|
||||
char c = GetChar(buffer, pos);
|
||||
if (IsUTF8ContinuationByte(c)) {
|
||||
result += direction;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t GetUTF32(Buffer *buffer, int64_t pos, int64_t *codepoint_size) {
|
||||
if (!InBounds(buffer, pos)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *p = buffer->data[buffer->bi] + pos;
|
||||
char *p = GetCharP(buffer, pos);
|
||||
int64_t max = buffer->len - pos;
|
||||
UTF32Result utf32 = UTF8ToUTF32(p, (int)max);
|
||||
Assert(utf32.error == 0);
|
||||
@@ -232,8 +269,8 @@ BufferIter Iterate(Buffer *buffer, Range range, int64_t direction = ITERATE_FORW
|
||||
Assert(direction == ITERATE_FORWARD || direction == ITERATE_BACKWARD);
|
||||
Assert(!IsUTF8ContinuationByte(GetChar(buffer, range.min)));
|
||||
Assert(range.max >= range.min);
|
||||
range.min = ClampMin(buffer, range.min);
|
||||
range.max = ClampMax(buffer, range.max);
|
||||
range.min = Clamp(buffer, range.min);
|
||||
range.max = Clamp(buffer, range.max);
|
||||
|
||||
BufferIter result = {buffer, range.min, range.max, direction};
|
||||
if (direction == ITERATE_BACKWARD) {
|
||||
@@ -255,7 +292,12 @@ void RunBufferTests() {
|
||||
String string = {buffer.data[buffer.bi], buffer.len};
|
||||
Assert(string == "Things and other things");
|
||||
Assert(buffer.lines.len == 1);
|
||||
Assert(buffer.lines[0] == "Things and other things");
|
||||
Assert(GetString(&buffer, buffer.lines[0]) == "Things and other things");
|
||||
|
||||
edits.clear();
|
||||
AddEdit(&edits, {1000, 1000}, " memes");
|
||||
ApplyEdits(&buffer, edits);
|
||||
Assert(GetString(&buffer, buffer.lines[0]) == "Things and other things memes");
|
||||
}
|
||||
{
|
||||
Buffer buffer = {scratch};
|
||||
@@ -275,7 +317,7 @@ void RunBufferTests() {
|
||||
String string = {buffer.data[buffer.bi], buffer.len};
|
||||
Assert(string == "Memes dna BigOther things");
|
||||
Assert(buffer.lines.len == 1);
|
||||
Assert(buffer.lines[0] == "Memes dna BigOther things");
|
||||
Assert(GetString(&buffer, buffer.lines[0]) == "Memes dna BigOther things");
|
||||
}
|
||||
{
|
||||
Buffer buffer = {scratch};
|
||||
@@ -287,9 +329,9 @@ void RunBufferTests() {
|
||||
});
|
||||
ApplyEdits(&buffer, edits);
|
||||
Assert(buffer.lines.len == 3);
|
||||
Assert(buffer.lines[1] == "Things and other things");
|
||||
Assert(buffer.lines[0] == "Things and other things");
|
||||
Assert(buffer.lines[2] == "");
|
||||
Assert(GetString(&buffer, buffer.lines[1]) == "Things and other things");
|
||||
Assert(GetString(&buffer, buffer.lines[0]) == "Things and other things");
|
||||
Assert(GetString(&buffer, buffer.lines[2]) == "");
|
||||
|
||||
{
|
||||
Array<char> s = {scratch};
|
||||
@@ -334,4 +376,26 @@ void RunBufferTests() {
|
||||
Assert(str.len == b.len);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Arena *arena = AllocArena();
|
||||
Buffer buffer = {*arena};
|
||||
Array<Edit> edits = {*arena};
|
||||
edits.add({
|
||||
{0, 0},
|
||||
"Things and other things\n"
|
||||
"Things and other things\n"
|
||||
});
|
||||
|
||||
int iters = 100;
|
||||
for (int i = 0; i < iters; i += 1) {
|
||||
ApplyEdits(&buffer, edits);
|
||||
for (int64_t j = 0; j < i; j += 1) {
|
||||
String string = GetString(&buffer, {edits[0].string.len * j, edits[0].string.len * (j + 1)});
|
||||
Assert(string == edits[0].string);
|
||||
}
|
||||
}
|
||||
Assert(edits[0].string.len * iters == buffer.len);
|
||||
Assert(buffer.lines.len == iters * 2 + 1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user