mirror of
https://github.com/beefytech/Beef.git
synced 2025-07-02 22:36:00 +02:00
Initial checkin
This commit is contained in:
parent
c74712dad9
commit
078564ac9e
3242 changed files with 1616395 additions and 0 deletions
105
BeefLibs/Beefy2D/src/utils/BeefPerf.bf
Normal file
105
BeefLibs/Beefy2D/src/utils/BeefPerf.bf
Normal file
|
@ -0,0 +1,105 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Beefy.utils
|
||||
{
|
||||
static class BeefPerf
|
||||
{
|
||||
[StdCall, CLink]
|
||||
extern static void BpInit(char8* severName, char8* sessionName);
|
||||
|
||||
[StdCall, CLink]
|
||||
extern static void BpShutdown();
|
||||
|
||||
[StdCall, CLink]
|
||||
extern static void BpRetryConnect();
|
||||
|
||||
[StdCall, CLink]
|
||||
extern static void BpPause();
|
||||
|
||||
[StdCall, CLink]
|
||||
extern static void BpUnpause();
|
||||
|
||||
[StdCall, CLink]
|
||||
extern static void BpSetThreadName(char8* threadName);
|
||||
|
||||
[StdCall, CLink]
|
||||
extern static void BpEnter(char8* name);
|
||||
|
||||
[StdCall, CLink]
|
||||
extern static void BpLeave();
|
||||
|
||||
[StdCall, CLink]
|
||||
extern static void BpEvent(char8* name, char8* details);
|
||||
|
||||
[StdCall, CLink]
|
||||
extern static char8* BpDynStr(char8* string);
|
||||
|
||||
public static void Init(StringView serverName, StringView sessionName)
|
||||
{
|
||||
BpInit(serverName.ToScopeCStr!(), sessionName.ToScopeCStr!());
|
||||
}
|
||||
|
||||
public static void RetryConnect()
|
||||
{
|
||||
BpRetryConnect();
|
||||
}
|
||||
|
||||
public static void Shutdown()
|
||||
{
|
||||
BpShutdown();
|
||||
}
|
||||
|
||||
public static void Pause()
|
||||
{
|
||||
BpPause();
|
||||
}
|
||||
|
||||
public static void Unpause()
|
||||
{
|
||||
BpUnpause();
|
||||
}
|
||||
|
||||
public static void SetThreadName(StringView threadName)
|
||||
{
|
||||
BpSetThreadName(threadName.ToScopeCStr!());
|
||||
}
|
||||
|
||||
[Inline]
|
||||
public static void Enter(char8* name)
|
||||
{
|
||||
BpEnter(name);
|
||||
}
|
||||
|
||||
[Inline]
|
||||
public static void Leave()
|
||||
{
|
||||
BpLeave();
|
||||
}
|
||||
|
||||
[Inline]
|
||||
public static void Event(char8* name, char8* details)
|
||||
{
|
||||
BpEvent(name, details);
|
||||
}
|
||||
|
||||
[Inline]
|
||||
public static char8* DynStr(char8* string)
|
||||
{
|
||||
return BpDynStr(string);
|
||||
}
|
||||
}
|
||||
|
||||
class AutoBeefPerf
|
||||
{
|
||||
public this(char8* name)
|
||||
{
|
||||
BeefPerf.Enter(name);
|
||||
}
|
||||
|
||||
public ~this()
|
||||
{
|
||||
BeefPerf.Leave();
|
||||
}
|
||||
}
|
||||
}
|
28
BeefLibs/Beefy2D/src/utils/DisposeProxy.bf
Normal file
28
BeefLibs/Beefy2D/src/utils/DisposeProxy.bf
Normal file
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Beefy.utils
|
||||
{
|
||||
public delegate void DisposeProxyDelegate();
|
||||
|
||||
public class DisposeProxy : IDisposable
|
||||
{
|
||||
public DisposeProxyDelegate mDisposeProxyDelegate;
|
||||
|
||||
public this(DisposeProxyDelegate theDelegate = null)
|
||||
{
|
||||
mDisposeProxyDelegate = theDelegate;
|
||||
}
|
||||
|
||||
public ~this()
|
||||
{
|
||||
delete mDisposeProxyDelegate;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
mDisposeProxyDelegate();
|
||||
}
|
||||
}
|
||||
}
|
12
BeefLibs/Beefy2D/src/utils/ISerializable.bf
Normal file
12
BeefLibs/Beefy2D/src/utils/ISerializable.bf
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Beefy.utils
|
||||
{
|
||||
public interface ISerializable
|
||||
{
|
||||
//TODO: void Serialize(StructuredData data);
|
||||
//TODO: void Deserialize(StructuredData data);
|
||||
}
|
||||
}
|
899
BeefLibs/Beefy2D/src/utils/IdSpan.bf
Normal file
899
BeefLibs/Beefy2D/src/utils/IdSpan.bf
Normal file
|
@ -0,0 +1,899 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Beefy.utils
|
||||
{
|
||||
public struct IdSpan
|
||||
{
|
||||
enum Change
|
||||
{
|
||||
case Insert(int32 index, int32 id, int16 length);
|
||||
case Remove(int32 index, int16 length);
|
||||
|
||||
public int32 GetIndex()
|
||||
{
|
||||
int32 index;
|
||||
switch (this)
|
||||
{
|
||||
case .Insert(out index, ?, ?):
|
||||
case .Remove(out index, ?):
|
||||
}
|
||||
return index;
|
||||
}
|
||||
}
|
||||
enum ChangeKind
|
||||
{
|
||||
case None;
|
||||
case Insert;
|
||||
case Remove;
|
||||
}
|
||||
|
||||
static uint8[] sEmptyData = new uint8[] { 0 } ~ delete _;
|
||||
|
||||
public uint8[] mData;
|
||||
public int32 mLength;
|
||||
List<Change> mChangeList;
|
||||
|
||||
public static int32 sId;
|
||||
public int32 mId = ++sId;
|
||||
|
||||
public bool mAlwaysPrepare = false;
|
||||
|
||||
/*public uint8[] mData
|
||||
{
|
||||
get
|
||||
{
|
||||
Debug.Assert(mChangeList == null);
|
||||
return mData;
|
||||
}
|
||||
}*/
|
||||
|
||||
public this()
|
||||
{
|
||||
mData = sEmptyData;
|
||||
mLength = 0;
|
||||
mChangeList = null;
|
||||
}
|
||||
|
||||
public this(uint8[] data, int32 length)
|
||||
{
|
||||
mData = data;
|
||||
mLength = length;
|
||||
mChangeList = null;
|
||||
}
|
||||
|
||||
bool HasChangeList
|
||||
{
|
||||
get
|
||||
{
|
||||
return mChangeList != null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEmpty
|
||||
{
|
||||
get
|
||||
{
|
||||
return mLength == 0;
|
||||
}
|
||||
}
|
||||
|
||||
struct Span
|
||||
{
|
||||
public int32 mIndex;
|
||||
public int32 mId;
|
||||
public int32 mLength;
|
||||
|
||||
public int32 mNext;
|
||||
}
|
||||
|
||||
public void PrepareReverse() mut
|
||||
{
|
||||
PrepareReverse(0, mChangeList.Count);
|
||||
DeleteAndNullify!(mChangeList);
|
||||
}
|
||||
|
||||
// Decode change lists, move backwards through it applying changes, the reencode it.
|
||||
// Repeats as necessary if items aren't in reverse order
|
||||
void PrepareReverse(int startChangeIdx, int endChangeIdx) mut
|
||||
{
|
||||
int changeIdx = startChangeIdx;
|
||||
|
||||
List<Span> spans = scope List<Span>();
|
||||
|
||||
while (changeIdx < endChangeIdx)
|
||||
{
|
||||
spans.Clear();
|
||||
|
||||
int encodeIdx = 0;
|
||||
int32 charId = 1;
|
||||
int32 charIdx = 0;
|
||||
while (true)
|
||||
{
|
||||
int32 cmd = Utils.DecodeInt(mData, ref encodeIdx);
|
||||
if (cmd > 0)
|
||||
{
|
||||
charId = cmd;
|
||||
}
|
||||
else
|
||||
{
|
||||
int32 spanSize = -cmd;
|
||||
|
||||
if (cmd == 0)
|
||||
break;
|
||||
|
||||
Span span;
|
||||
span.mId = charId;
|
||||
span.mIndex = charIdx;
|
||||
span.mLength = spanSize;
|
||||
span.mNext = -1;
|
||||
spans.Add(span);
|
||||
|
||||
charId += spanSize;
|
||||
charIdx += spanSize;
|
||||
|
||||
if (spans.Count > 1)
|
||||
spans[spans.Count - 2].mNext = (int32)spans.Count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize to something so we can pick up the insert
|
||||
if (spans.Count == 0)
|
||||
{
|
||||
Span span;
|
||||
span.mId = 1;
|
||||
span.mIndex = 0;
|
||||
span.mLength = 0;
|
||||
span.mNext = -1;
|
||||
spans.Add(span);
|
||||
}
|
||||
|
||||
|
||||
int32 index = -1;
|
||||
int32 length = -1;
|
||||
int32 curId = -1;
|
||||
ChangeKind changeKind = .None;
|
||||
|
||||
int32 checkIdx = (int32)spans.Count - 1;
|
||||
int32 headSpanIdx = 0;
|
||||
|
||||
// Reverse find the first span
|
||||
while ((checkIdx >= 0) && (changeIdx < endChangeIdx))
|
||||
{
|
||||
if (changeKind == .None)
|
||||
{
|
||||
switch (mChangeList[changeIdx])
|
||||
{
|
||||
case .Insert(out index, out curId, out length):
|
||||
changeKind = .Insert;
|
||||
case .Remove(out index, out length):
|
||||
changeKind = .Remove;
|
||||
}
|
||||
}
|
||||
|
||||
var checkSpan = ref spans[checkIdx];
|
||||
if ((index >= checkSpan.mIndex) && (index <= checkSpan.mIndex + checkSpan.mLength))
|
||||
{
|
||||
if (changeKind == .Insert)
|
||||
{
|
||||
if (index == checkSpan.mIndex)
|
||||
{
|
||||
if (checkSpan.mLength == 0)
|
||||
{
|
||||
checkSpan.mLength = length;
|
||||
checkSpan.mId = curId;
|
||||
}
|
||||
else
|
||||
{
|
||||
int32 newSpanIdx = (.)spans.Count;
|
||||
|
||||
// Insert before span
|
||||
Span span;
|
||||
span.mIndex = index;
|
||||
span.mId = curId;
|
||||
span.mLength = length;
|
||||
span.mNext = (.)checkIdx;
|
||||
spans.Add(span);
|
||||
|
||||
if (checkIdx > 0)
|
||||
{
|
||||
Debug.Assert(spans[checkIdx - 1].mNext == checkIdx);
|
||||
spans[checkIdx - 1].mNext = newSpanIdx;
|
||||
}
|
||||
else
|
||||
headSpanIdx = newSpanIdx;
|
||||
|
||||
// Since we remapped the previous entries mNext, we are no longer in order and can't be reused this pass
|
||||
checkIdx = checkIdx - 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var checkSpan;
|
||||
|
||||
// Split span
|
||||
int32 leftLength = index - checkSpan.mIndex;
|
||||
|
||||
int32 newSpanId = (.)spans.Count;
|
||||
int32 newRightId = (.)spans.Count + 1;
|
||||
@checkSpan.mNext = newSpanId;
|
||||
@checkSpan.mLength = leftLength;
|
||||
|
||||
Span span;
|
||||
span.mIndex = index;
|
||||
span.mId = curId;
|
||||
span.mLength = length;
|
||||
span.mNext = newRightId;
|
||||
spans.Add(span);
|
||||
|
||||
Span rightSpan;
|
||||
rightSpan.mIndex = index + span.mLength;
|
||||
rightSpan.mId = checkSpan.mId + leftLength;
|
||||
rightSpan.mLength = checkSpan.mLength - leftLength;
|
||||
rightSpan.mNext = checkSpan.mNext;
|
||||
spans.Add(rightSpan);
|
||||
}
|
||||
}
|
||||
else // Remove
|
||||
{
|
||||
int removeSpanIdx = checkIdx;
|
||||
if (index == checkSpan.mIndex)
|
||||
{
|
||||
// Removing from front of span. Handled in loop below.
|
||||
}
|
||||
else if (index + length >= checkSpan.mIndex + checkSpan.mLength)
|
||||
{
|
||||
// Removing up to or past end of span
|
||||
int32 removeCount = Math.Min(length, checkSpan.mLength - (index - checkSpan.mIndex));
|
||||
checkSpan.mLength -= removeCount;
|
||||
length -= removeCount;
|
||||
removeSpanIdx = checkSpan.mNext;
|
||||
}
|
||||
else
|
||||
{
|
||||
var checkSpan;
|
||||
|
||||
int32 splitIdx = index - checkSpan.mIndex;
|
||||
int32 splitOfs = splitIdx + length;
|
||||
int32 newRightId = (.)spans.Count;
|
||||
@checkSpan.mNext = newRightId;
|
||||
@checkSpan.mLength = index - checkSpan.mIndex;
|
||||
|
||||
Span rightSpan;
|
||||
rightSpan.mIndex = checkSpan.mIndex + splitIdx;
|
||||
rightSpan.mId = checkSpan.mId + splitOfs;
|
||||
rightSpan.mLength = checkSpan.mLength - splitOfs;
|
||||
rightSpan.mNext = checkSpan.mNext;
|
||||
spans.Add(rightSpan);
|
||||
|
||||
length = 0;
|
||||
|
||||
if (newRightId == checkIdx + 1)
|
||||
checkIdx = newRightId; // rightSpan index is valid now and it is next in sequence
|
||||
}
|
||||
|
||||
while (length > 0)
|
||||
{
|
||||
var removeSpan = ref spans[removeSpanIdx];
|
||||
|
||||
// Remove from start of span
|
||||
int32 removeCount = Math.Min(removeSpan.mLength, length);
|
||||
removeSpan.mId += removeCount;
|
||||
removeSpan.mLength -= removeCount;
|
||||
|
||||
length -= removeCount;
|
||||
|
||||
removeSpanIdx = removeSpan.mNext;
|
||||
}
|
||||
}
|
||||
|
||||
changeIdx++;
|
||||
changeKind = .None;
|
||||
continue;
|
||||
}
|
||||
|
||||
checkIdx--;
|
||||
}
|
||||
|
||||
curId = 1;
|
||||
int32 spanIdx = headSpanIdx;
|
||||
int curEncodeIdx = 0;
|
||||
if (mData != sEmptyData)
|
||||
delete mData;
|
||||
mData = new uint8[spans.Count * 8];
|
||||
|
||||
while (spanIdx != -1)
|
||||
{
|
||||
var span = ref spans[spanIdx];
|
||||
|
||||
if (span.mLength == 0)
|
||||
{
|
||||
spanIdx = span.mNext;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (span.mId != curId)
|
||||
{
|
||||
Utils.EncodeInt(mData, ref curEncodeIdx, span.mId);
|
||||
curId = span.mId;
|
||||
}
|
||||
|
||||
Utils.EncodeInt(mData, ref curEncodeIdx, -span.mLength);
|
||||
curId += span.mLength;
|
||||
|
||||
spanIdx = span.mNext;
|
||||
}
|
||||
Utils.EncodeInt(mData, ref curEncodeIdx, 0);
|
||||
mLength = (int32)curEncodeIdx;
|
||||
}
|
||||
}
|
||||
|
||||
void MaybePrepare() mut
|
||||
{
|
||||
// For sanity - only queue up so many changes
|
||||
if (mChangeList.Count >= 8192)
|
||||
{
|
||||
Prepare();
|
||||
}
|
||||
}
|
||||
|
||||
public void Prepare() mut
|
||||
{
|
||||
if (mChangeList == null)
|
||||
return;
|
||||
|
||||
scope AutoBeefPerf("IdSpan.Prepare");
|
||||
|
||||
int changeIdx = 0;
|
||||
|
||||
while (changeIdx < mChangeList.Count)
|
||||
{
|
||||
// Check to see if we have a reverse-order encoding. This can occur when undoing forward-ordered changes (for example)
|
||||
bool hasReverse = false;
|
||||
int reverseLastIdx = changeIdx;
|
||||
|
||||
int32 prevIndex = mChangeList[changeIdx].GetIndex();
|
||||
for (int checkIdx = changeIdx + 1; checkIdx < mChangeList.Count; checkIdx++)
|
||||
{
|
||||
int32 nextIndex = mChangeList[checkIdx].GetIndex();
|
||||
if (nextIndex > prevIndex)
|
||||
break;
|
||||
if (nextIndex < prevIndex)
|
||||
hasReverse = true;
|
||||
reverseLastIdx = checkIdx;
|
||||
prevIndex = nextIndex;
|
||||
}
|
||||
|
||||
if (hasReverse)
|
||||
{
|
||||
PrepareReverse(changeIdx, reverseLastIdx + 1);
|
||||
changeIdx = reverseLastIdx + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// We need space to encode '-length', the new span ID,
|
||||
// reverting back to the original ID, and a split span length
|
||||
uint8[] textIdData = new uint8[mLength + mChangeList.Count*16];
|
||||
|
||||
int prevCharIdx = 0;
|
||||
int prevEncodeIdx = 0;
|
||||
int prevLastSpanLength = 0;
|
||||
int prevLastSpanIdStart = 1;
|
||||
|
||||
int curEncodeIdx = 0;
|
||||
int curSpanIdStart = 1;
|
||||
bool foundSpan = false;
|
||||
int ignoreLength = 0;
|
||||
|
||||
int32 index;
|
||||
int32 length;
|
||||
int32 curId = -1;
|
||||
ChangeKind changeKind;
|
||||
|
||||
|
||||
|
||||
switch (mChangeList[changeIdx++])
|
||||
{
|
||||
case .Insert(out index, out curId, out length):
|
||||
changeKind = .Insert;
|
||||
case .Remove(out index, out length):
|
||||
changeKind = .Remove;
|
||||
}
|
||||
|
||||
while (prevLastSpanIdStart != -1)
|
||||
{
|
||||
if (ignoreLength > 0)
|
||||
{
|
||||
int handleLength = Math.Min(prevLastSpanLength, ignoreLength);
|
||||
ignoreLength -= handleLength;
|
||||
prevLastSpanIdStart += handleLength;
|
||||
prevLastSpanLength -= handleLength;
|
||||
}
|
||||
|
||||
if ((curSpanIdStart != prevLastSpanIdStart) && (prevLastSpanLength > 0) && (ignoreLength == 0))
|
||||
{
|
||||
Utils.EncodeInt(textIdData, ref curEncodeIdx, prevLastSpanIdStart);
|
||||
curSpanIdStart = prevLastSpanIdStart;
|
||||
}
|
||||
|
||||
if ((prevCharIdx + prevLastSpanLength >= index) && (!foundSpan) && (ignoreLength == 0))
|
||||
{
|
||||
foundSpan = true;
|
||||
|
||||
if (curSpanIdStart != prevLastSpanIdStart)
|
||||
{
|
||||
Utils.EncodeInt(textIdData, ref curEncodeIdx, prevLastSpanIdStart);
|
||||
curSpanIdStart = prevLastSpanIdStart;
|
||||
}
|
||||
|
||||
if (changeKind case .Insert)
|
||||
{
|
||||
// Time to insert
|
||||
int leftSplitLen = index - prevCharIdx;
|
||||
if (leftSplitLen > 0)
|
||||
{
|
||||
Utils.EncodeInt(textIdData, ref curEncodeIdx, -leftSplitLen);
|
||||
curSpanIdStart += leftSplitLen;
|
||||
prevLastSpanIdStart += leftSplitLen;
|
||||
prevCharIdx += leftSplitLen;
|
||||
prevLastSpanLength -= leftSplitLen;
|
||||
}
|
||||
|
||||
if (curSpanIdStart != curId)
|
||||
{
|
||||
curSpanIdStart = curId;
|
||||
Utils.EncodeInt(textIdData, ref curEncodeIdx, curSpanIdStart);
|
||||
}
|
||||
curId += length;
|
||||
|
||||
if (length > 0)
|
||||
{
|
||||
Utils.EncodeInt(textIdData, ref curEncodeIdx, -length);
|
||||
curSpanIdStart += length;
|
||||
prevCharIdx += length;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ignoreLength = length;
|
||||
|
||||
// Time to insert
|
||||
int leftSplitLen = index - prevCharIdx;
|
||||
if (leftSplitLen > 0)
|
||||
{
|
||||
Utils.EncodeInt(textIdData, ref curEncodeIdx, -leftSplitLen);
|
||||
curSpanIdStart += leftSplitLen;
|
||||
prevLastSpanIdStart += leftSplitLen;
|
||||
prevCharIdx += leftSplitLen;
|
||||
prevLastSpanLength -= leftSplitLen;
|
||||
}
|
||||
}
|
||||
|
||||
if (changeIdx < mChangeList.Count)
|
||||
{
|
||||
switch (mChangeList[changeIdx])
|
||||
{
|
||||
case .Insert(out index, out curId, out length):
|
||||
changeKind = .Insert;
|
||||
case .Remove(out index, out length):
|
||||
changeKind = .Remove;
|
||||
}
|
||||
if (index >= prevCharIdx)
|
||||
{
|
||||
// We are inserting into a just-removed location
|
||||
foundSpan = false;
|
||||
changeIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
int cmd = Utils.DecodeInt(mData, ref prevEncodeIdx);
|
||||
if (cmd >= 0)
|
||||
{
|
||||
if (prevLastSpanLength > 0)
|
||||
{
|
||||
Utils.EncodeInt(textIdData, ref curEncodeIdx, -prevLastSpanLength);
|
||||
curSpanIdStart += prevLastSpanLength;
|
||||
prevLastSpanIdStart += prevLastSpanLength;
|
||||
prevCharIdx += prevLastSpanLength;
|
||||
prevLastSpanLength = 0;
|
||||
}
|
||||
|
||||
Debug.Assert(prevLastSpanLength == 0);
|
||||
prevLastSpanIdStart = cmd;
|
||||
|
||||
if (cmd == 0)
|
||||
break;
|
||||
}
|
||||
else
|
||||
prevLastSpanLength += -cmd;
|
||||
}
|
||||
|
||||
Utils.EncodeInt(textIdData, ref curEncodeIdx, 0);
|
||||
mLength = (int32)curEncodeIdx;
|
||||
if (mData != sEmptyData)
|
||||
delete mData;
|
||||
mData = textIdData;
|
||||
}
|
||||
DeleteAndNullify!(mChangeList);
|
||||
}
|
||||
|
||||
public IdSpan GetPrepared() mut
|
||||
{
|
||||
Prepare();
|
||||
return this;
|
||||
}
|
||||
|
||||
public void Dispose() mut
|
||||
{
|
||||
if (mData != sEmptyData)
|
||||
delete mData;
|
||||
delete mChangeList;
|
||||
mData = sEmptyData;
|
||||
mLength = 0;
|
||||
}
|
||||
|
||||
public void DuplicateFrom(ref IdSpan span) mut
|
||||
{
|
||||
Dispose();
|
||||
this = span.Duplicate();
|
||||
}
|
||||
|
||||
public static readonly IdSpan Empty = IdSpan(sEmptyData, 1);
|
||||
|
||||
public void Insert(int index, int length, ref int32 curId) mut
|
||||
{
|
||||
var index;
|
||||
var length;
|
||||
|
||||
if (mChangeList == null)
|
||||
mChangeList = new .();
|
||||
else if (mChangeList.Count > 0)
|
||||
{
|
||||
var change = ref mChangeList.Back;
|
||||
if (change case .Insert(let prevIndex, let prevId, var ref prevLength))
|
||||
{
|
||||
if ((prevIndex + prevLength == index) && (prevId + prevLength == curId))
|
||||
{
|
||||
int16 curLen = (int16)Math.Min(length, 0x7FFF - prevLength);
|
||||
prevLength += curLen;
|
||||
|
||||
curId += curLen;
|
||||
index += curLen;
|
||||
length -= curLen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (length > 0)
|
||||
{
|
||||
int16 curLen = (int16)Math.Min(length, 0x7FFF);
|
||||
mChangeList.Add(.Insert((int32)index, curId, curLen));
|
||||
|
||||
curId += curLen;
|
||||
index += curLen;
|
||||
length -= curLen;
|
||||
}
|
||||
|
||||
if (mAlwaysPrepare)
|
||||
Prepare();
|
||||
else
|
||||
MaybePrepare();
|
||||
}
|
||||
|
||||
public void Remove(int index, int length) mut
|
||||
{
|
||||
var index;
|
||||
var length;
|
||||
|
||||
if (mChangeList == null)
|
||||
mChangeList = new .();
|
||||
else if (mChangeList.Count > 0)
|
||||
{
|
||||
var change = ref mChangeList.Back;
|
||||
if (change case .Remove(let prevIndex, var ref prevLength))
|
||||
{
|
||||
if (prevIndex == index)
|
||||
{
|
||||
int16 curLen = (int16)Math.Min(length, 0x7FFF - prevLength);
|
||||
prevLength += curLen;
|
||||
length -= curLen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (length > 0)
|
||||
{
|
||||
int16 curLen = (int16)Math.Min(length, 0x7FFF);
|
||||
mChangeList.Add(.Remove((int32)index, curLen));
|
||||
length -= curLen;
|
||||
}
|
||||
|
||||
if (mAlwaysPrepare)
|
||||
Prepare();
|
||||
else
|
||||
MaybePrepare();
|
||||
}
|
||||
|
||||
public int GetIndexFromId(int32 findCharId)
|
||||
{
|
||||
Debug.Assert(!HasChangeList);
|
||||
int encodeIdx = 0;
|
||||
int charId = 1;
|
||||
int charIdx = 0;
|
||||
while (true)
|
||||
{
|
||||
int cmd = Utils.DecodeInt(mData, ref encodeIdx);
|
||||
if (cmd > 0)
|
||||
charId = cmd;
|
||||
else
|
||||
{
|
||||
int spanSize = -cmd;
|
||||
if ((findCharId >= charId) && (findCharId < charId + spanSize))
|
||||
return charIdx + (findCharId - charId);
|
||||
charId += spanSize;
|
||||
charIdx += spanSize;
|
||||
|
||||
if (cmd == 0)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int32 GetIdAtIndex(int findIndex)
|
||||
{
|
||||
int encodeIdx = 0;
|
||||
int32 charId = 1;
|
||||
int charIdx = 0;
|
||||
while (true)
|
||||
{
|
||||
int32 cmd = Utils.DecodeInt(mData, ref encodeIdx);
|
||||
if (cmd > 0)
|
||||
charId = cmd;
|
||||
else
|
||||
{
|
||||
int32 spanSize = -cmd;
|
||||
if ((findIndex >= charIdx) && (findIndex < charIdx + spanSize))
|
||||
return charId + (int32)(findIndex - charIdx);
|
||||
charId += spanSize;
|
||||
charIdx += spanSize;
|
||||
|
||||
if (cmd == 0)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IdSpan Duplicate()
|
||||
{
|
||||
Debug.Assert(!HasChangeList);
|
||||
IdSpan idSpan = IdSpan();
|
||||
if (mData != null)
|
||||
{
|
||||
idSpan.mData = new uint8[mLength];
|
||||
mData.CopyTo(idSpan.mData, 0, 0, mLength);
|
||||
idSpan.mLength = mLength;
|
||||
}
|
||||
return idSpan;
|
||||
}
|
||||
|
||||
public bool Equals(IdSpan idData2)
|
||||
{
|
||||
Debug.Assert(!HasChangeList);
|
||||
Debug.Assert(!idData2.HasChangeList);
|
||||
|
||||
if ((mLength == 0) || (idData2.mLength == 0))
|
||||
return (mLength == 0) && (idData2.mLength == 0);
|
||||
|
||||
int encodeIdx1 = 0;
|
||||
int encodeIdx2 = 0;
|
||||
|
||||
int curSpanId1 = 1;
|
||||
int curSpanId2 = 1;
|
||||
|
||||
int spanLen1 = 0;
|
||||
int spanLen2 = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
while (spanLen1 == 0)
|
||||
{
|
||||
int cmd = Utils.DecodeInt(mData, ref encodeIdx1);
|
||||
if (cmd < 0)
|
||||
{
|
||||
spanLen1 = -cmd;
|
||||
}
|
||||
else
|
||||
{
|
||||
curSpanId1 = cmd;
|
||||
if (cmd == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (spanLen2 == 0)
|
||||
{
|
||||
int32 cmd = Utils.DecodeInt(idData2.mData, ref encodeIdx2);
|
||||
if (cmd < 0)
|
||||
{
|
||||
spanLen2 = -cmd;
|
||||
}
|
||||
else
|
||||
{
|
||||
curSpanId2 = cmd;
|
||||
if (cmd == 0)
|
||||
{
|
||||
// They are equal if both spans are at the end
|
||||
return spanLen1 == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (curSpanId1 != curSpanId2)
|
||||
return false;
|
||||
int minLen = Math.Min(spanLen1, spanLen2);
|
||||
curSpanId1 += minLen;
|
||||
spanLen1 -= minLen;
|
||||
curSpanId2 += minLen;
|
||||
spanLen2 -= minLen;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetTotalLength()
|
||||
{
|
||||
int len = 0;
|
||||
int encodeIdx = 0;
|
||||
while (true)
|
||||
{
|
||||
int cmd = Utils.DecodeInt(mData, ref encodeIdx);
|
||||
if (cmd == 0)
|
||||
return len;
|
||||
if (cmd < 0)
|
||||
len += -cmd;
|
||||
}
|
||||
}
|
||||
|
||||
static bool FindId(uint8[] idData, ref int encodeIdx, ref int32 curSpanId, ref int32 spanLen, int32 findSpanId)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
int32 cmd = Utils.DecodeInt(idData, ref encodeIdx);
|
||||
if (cmd < 0)
|
||||
{
|
||||
spanLen = -cmd;
|
||||
if ((findSpanId >= curSpanId) && (findSpanId < curSpanId + spanLen))
|
||||
{
|
||||
int32 delta = findSpanId - curSpanId;
|
||||
curSpanId += delta;
|
||||
spanLen -= delta;
|
||||
return true;
|
||||
}
|
||||
curSpanId += spanLen;
|
||||
}
|
||||
else
|
||||
{
|
||||
curSpanId = cmd;
|
||||
if (cmd == 0)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRangeEqual(IdSpan idData2, int32 startCharId, int32 endCharId)
|
||||
{
|
||||
int encodeIdx1 = 0;
|
||||
int encodeIdx2 = 0;
|
||||
|
||||
int32 curSpanId1 = 1;
|
||||
int32 curSpanId2 = 1;
|
||||
|
||||
int32 spanLen1 = 0;
|
||||
int32 spanLen2 = 0;
|
||||
|
||||
if (!FindId(mData, ref encodeIdx1, ref curSpanId1, ref spanLen1, startCharId))
|
||||
return false;
|
||||
if (!FindId(idData2.mData, ref encodeIdx2, ref curSpanId2, ref spanLen2, startCharId))
|
||||
return false;
|
||||
|
||||
while (true)
|
||||
{
|
||||
while (spanLen1 == 0)
|
||||
{
|
||||
int32 cmd = Utils.DecodeInt(mData, ref encodeIdx1);
|
||||
if (cmd < 0)
|
||||
{
|
||||
spanLen1 = -cmd;
|
||||
}
|
||||
else
|
||||
{
|
||||
curSpanId1 = cmd;
|
||||
if (cmd == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (spanLen2 == 0)
|
||||
{
|
||||
int32 cmd = Utils.DecodeInt(idData2.mData, ref encodeIdx2);
|
||||
if (cmd < 0)
|
||||
{
|
||||
spanLen2 = -cmd;
|
||||
}
|
||||
else
|
||||
{
|
||||
curSpanId2 = cmd;
|
||||
if (cmd == 0)
|
||||
{
|
||||
// They are equal if both spans are at the end
|
||||
return spanLen1 == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (curSpanId1 != curSpanId2)
|
||||
return false;
|
||||
if (curSpanId1 == endCharId)
|
||||
return true;
|
||||
|
||||
int minLen = Math.Min(spanLen1, spanLen2);
|
||||
if ((endCharId >= curSpanId1) && (endCharId < curSpanId1 + minLen))
|
||||
minLen = Math.Min(minLen, endCharId - curSpanId1);
|
||||
if ((endCharId >= curSpanId2) && (endCharId < curSpanId2 + minLen))
|
||||
minLen = Math.Min(minLen, endCharId - curSpanId2);
|
||||
|
||||
curSpanId1 += (int32)minLen;
|
||||
spanLen1 -= (.)minLen;
|
||||
curSpanId2 += (int32)minLen;
|
||||
spanLen2 -= (.)minLen;
|
||||
}
|
||||
}
|
||||
|
||||
public static IdSpan GetDefault(int32 length)
|
||||
{
|
||||
uint8[] idData = new uint8[8];
|
||||
int encodeIdx = 0;
|
||||
Utils.EncodeInt(idData, ref encodeIdx, (int32)-length);
|
||||
Utils.EncodeInt(idData, ref encodeIdx, 0);
|
||||
|
||||
IdSpan idSpan = IdSpan();
|
||||
idSpan.mData = idData;
|
||||
idSpan.mLength = (int32)encodeIdx;
|
||||
return idSpan;
|
||||
}
|
||||
|
||||
public void Dump() mut
|
||||
{
|
||||
Prepare();
|
||||
Debug.WriteLine("IdSpan Dump:");
|
||||
int encodeIdx = 0;
|
||||
int charId = 1;
|
||||
int charIdx = 0;
|
||||
while (true)
|
||||
{
|
||||
int32 cmd = Utils.DecodeInt(mData, ref encodeIdx);
|
||||
if (cmd > 0)
|
||||
{
|
||||
charId = cmd;
|
||||
Debug.WriteLine(" Id: {0}", charId);
|
||||
}
|
||||
else
|
||||
{
|
||||
int32 spanSize = -cmd;
|
||||
|
||||
charId += spanSize;
|
||||
charIdx += spanSize;
|
||||
|
||||
if (cmd == 0)
|
||||
return;
|
||||
|
||||
Debug.WriteLine(" Len: {0}", spanSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
17
BeefLibs/Beefy2D/src/utils/ManualBreak.bf
Normal file
17
BeefLibs/Beefy2D/src/utils/ManualBreak.bf
Normal file
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Runtime;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Beefy.utils
|
||||
{
|
||||
public static class ManualBreak
|
||||
{
|
||||
public static void Break()
|
||||
{
|
||||
ThrowUnimplemented();
|
||||
//Debugger.Break();
|
||||
}
|
||||
}
|
||||
}
|
79
BeefLibs/Beefy2D/src/utils/PerfTimer.bf
Normal file
79
BeefLibs/Beefy2D/src/utils/PerfTimer.bf
Normal file
|
@ -0,0 +1,79 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Beefy.utils
|
||||
{
|
||||
public abstract class PerfTimer
|
||||
{
|
||||
[StdCall, CLink]
|
||||
extern static void PerfTimer_ZoneStart(char8* name);
|
||||
|
||||
[StdCall, CLink]
|
||||
extern static void PerfTimer_ZoneEnd();
|
||||
|
||||
[StdCall, CLink]
|
||||
extern static void PerfTimer_Message(char8* theString);
|
||||
|
||||
[StdCall, CLink]
|
||||
extern static int32 PerfTimer_IsRecording();
|
||||
|
||||
[StdCall, CLink]
|
||||
extern static void PerfTimer_StartRecording();
|
||||
|
||||
[StdCall, CLink]
|
||||
extern static void PerfTimer_StopRecording();
|
||||
|
||||
[StdCall, CLink]
|
||||
extern static void PerfTimer_DbgPrint();
|
||||
|
||||
static DisposeProxy mZoneEndDisposeProxy ~ delete _;
|
||||
|
||||
public static DisposeProxy ZoneStart(String name)
|
||||
{
|
||||
if (mZoneEndDisposeProxy == null)
|
||||
mZoneEndDisposeProxy = new DisposeProxy(new => ZoneEnd);
|
||||
|
||||
PerfTimer_ZoneStart(name);
|
||||
return mZoneEndDisposeProxy;
|
||||
}
|
||||
|
||||
public static void ZoneEnd()
|
||||
{
|
||||
PerfTimer_ZoneEnd();
|
||||
}
|
||||
|
||||
public static void Message(String theString)
|
||||
{
|
||||
PerfTimer_Message(theString);
|
||||
}
|
||||
|
||||
public static void Message(String format, params Object[] theParams)
|
||||
{
|
||||
String outStr = scope String();
|
||||
outStr.AppendF(format, params theParams);
|
||||
Message(outStr);
|
||||
}
|
||||
|
||||
public static bool IsRecording()
|
||||
{
|
||||
return PerfTimer_IsRecording() != 0;
|
||||
}
|
||||
|
||||
public static void StartRecording()
|
||||
{
|
||||
PerfTimer_StartRecording();
|
||||
}
|
||||
|
||||
public static void StopRecording()
|
||||
{
|
||||
PerfTimer_StopRecording();
|
||||
}
|
||||
|
||||
public static void DbgPrint()
|
||||
{
|
||||
PerfTimer_DbgPrint();
|
||||
}
|
||||
}
|
||||
}
|
74
BeefLibs/Beefy2D/src/utils/SmoothValue.bf
Normal file
74
BeefLibs/Beefy2D/src/utils/SmoothValue.bf
Normal file
|
@ -0,0 +1,74 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Beefy.utils
|
||||
{
|
||||
public class SmoothValue
|
||||
{
|
||||
public double mSrc;
|
||||
public double mDest;
|
||||
public float mPct;
|
||||
public float mSpeed = 1.0f;
|
||||
public float mSpeedScale = 1.0f;
|
||||
|
||||
public double v
|
||||
{
|
||||
get
|
||||
{
|
||||
if (mPct == 1.0f)
|
||||
return mDest;
|
||||
return mSrc + (mDest - mSrc) * EasedPct;
|
||||
}
|
||||
}
|
||||
|
||||
public double EasedPct
|
||||
{
|
||||
get
|
||||
{
|
||||
if (mPct == 1.0f)
|
||||
return 1.0f;
|
||||
return Utils.EaseInAndOut(mPct);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsMoving
|
||||
{
|
||||
get { return mPct != 1.0f; }
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
mPct = Math.Min(1.0f, mPct + mSpeed * mSpeedScale);
|
||||
}
|
||||
|
||||
public void Set(double val, bool immediate = false)
|
||||
{
|
||||
if ((!immediate) && (val == mDest))
|
||||
return;
|
||||
|
||||
if ((mPct != 1.0f) && (mPct != 0.0f))
|
||||
{
|
||||
double cur = v;
|
||||
|
||||
if (mPct > 0.80f)
|
||||
mPct = 0.80f;
|
||||
|
||||
mDest = val;
|
||||
mSrc = -(cur - mDest * EasedPct) / (EasedPct - 1);
|
||||
mSpeedScale = (1.0f - mPct);
|
||||
}
|
||||
else
|
||||
{
|
||||
mSrc = v;
|
||||
mPct = 0.0f;
|
||||
mSpeedScale = 1.0f;
|
||||
mDest = val;
|
||||
}
|
||||
|
||||
|
||||
if (immediate)
|
||||
mPct = 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
2552
BeefLibs/Beefy2D/src/utils/StructuredData.bf
Normal file
2552
BeefLibs/Beefy2D/src/utils/StructuredData.bf
Normal file
File diff suppressed because it is too large
Load diff
161
BeefLibs/Beefy2D/src/utils/TextSearcher.bf
Normal file
161
BeefLibs/Beefy2D/src/utils/TextSearcher.bf
Normal file
|
@ -0,0 +1,161 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Beefy.utils
|
||||
{
|
||||
class TextSearcher
|
||||
{
|
||||
enum CmdKind
|
||||
{
|
||||
Contains,
|
||||
NotContains,
|
||||
Equals,
|
||||
}
|
||||
|
||||
struct CmdElement
|
||||
{
|
||||
public CmdKind mKind;
|
||||
public String mString;
|
||||
}
|
||||
|
||||
List<CmdElement> mCmds = new List<CmdElement>() ~
|
||||
{
|
||||
for (var cmdElement in mCmds)
|
||||
{
|
||||
delete cmdElement.mString;
|
||||
}
|
||||
delete _;
|
||||
};
|
||||
|
||||
Result<void> EndCmd(String findStr, ref char8 startChar, String outError, OperatorDelegate operatorHandler)
|
||||
{
|
||||
if (findStr.IsEmpty)
|
||||
return .Ok;
|
||||
|
||||
CmdElement cmdElement;
|
||||
|
||||
if ((findStr.StartsWith(":")) && (operatorHandler != null))
|
||||
{
|
||||
int opIdx = -1;
|
||||
for (int i < findStr.Length)
|
||||
{
|
||||
char8 c = findStr[i];
|
||||
if ((c == '=') || (c == '>') || (c == '<'))
|
||||
{
|
||||
if (opIdx != -1)
|
||||
{
|
||||
outError.Append("Multiple operators cannot be defined in the same text segment");
|
||||
return .Err;
|
||||
}
|
||||
opIdx = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (opIdx != -1)
|
||||
{
|
||||
if (startChar != 0)
|
||||
{
|
||||
outError.Append("Multiple operators cannot be defined in the same text segment");
|
||||
return .Err;
|
||||
}
|
||||
|
||||
if ((operatorHandler == null) || (!operatorHandler(scope String(findStr, 0, opIdx), findStr[opIdx], scope String(findStr, opIdx + 1))))
|
||||
{
|
||||
outError.AppendF("Invalid expression: {0}", findStr);
|
||||
return .Err;
|
||||
}
|
||||
return .Ok;
|
||||
}
|
||||
}
|
||||
|
||||
if (startChar == '=')
|
||||
cmdElement.mKind = .Equals;
|
||||
else if (startChar == '-')
|
||||
cmdElement.mKind = .NotContains;
|
||||
else
|
||||
cmdElement.mKind = .Contains;
|
||||
cmdElement.mString = new String(findStr);
|
||||
mCmds.Add(cmdElement);
|
||||
|
||||
findStr.Clear();
|
||||
startChar = 0;
|
||||
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
delegate bool OperatorDelegate(String lhs, char8 op, String rhs);
|
||||
public Result<void> Init(String searchStr, String outError, OperatorDelegate operatorHandler = null)
|
||||
{
|
||||
bool inQuote = false;
|
||||
|
||||
char8 startChar = 0;
|
||||
|
||||
String findStr = scope String();
|
||||
|
||||
for (int i < searchStr.Length)
|
||||
{
|
||||
var c = searchStr[i];
|
||||
if (c == '\"')
|
||||
{
|
||||
inQuote = !inQuote;
|
||||
continue;
|
||||
}
|
||||
if (c == '\"')
|
||||
{
|
||||
c = searchStr[++i];
|
||||
findStr.Append(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((c == ' ') && (!inQuote))
|
||||
{
|
||||
Try!(EndCmd(findStr, ref startChar, outError, operatorHandler));
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((startChar == 0) && (findStr.IsEmpty) && (!inQuote))
|
||||
{
|
||||
if ((c == '=') || (c == '-'))
|
||||
{
|
||||
startChar = c;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
findStr.Append(c);
|
||||
}
|
||||
Try!(EndCmd(findStr, ref startChar, outError, operatorHandler));
|
||||
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public bool Matches(String checkStr)
|
||||
{
|
||||
for (var cmd in ref mCmds)
|
||||
{
|
||||
switch (cmd.mKind)
|
||||
{
|
||||
case .Contains:
|
||||
if (checkStr.IndexOf(cmd.mString, true) == -1)
|
||||
return false;
|
||||
case .NotContains:
|
||||
if (checkStr.IndexOf(cmd.mString, true) != -1)
|
||||
return false;
|
||||
case .Equals:
|
||||
if (!String.Equals(checkStr, cmd.mString, .OrdinalIgnoreCase))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsEmpty
|
||||
{
|
||||
get
|
||||
{
|
||||
return mCmds.Count == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
311
BeefLibs/Beefy2D/src/utils/UndoManager.bf
Normal file
311
BeefLibs/Beefy2D/src/utils/UndoManager.bf
Normal file
|
@ -0,0 +1,311 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Beefy.utils
|
||||
{
|
||||
public class UndoAction
|
||||
{
|
||||
public virtual bool Merge(UndoAction nextAction)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool Undo()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool Redo()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual int32 GetCost()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IUndoBatchStart
|
||||
{
|
||||
IUndoBatchEnd BatchEnd { get; }
|
||||
String Name { get; }
|
||||
}
|
||||
|
||||
public interface IUndoBatchEnd
|
||||
{
|
||||
IUndoBatchStart BatchStart { get; }
|
||||
String Name { get; }
|
||||
}
|
||||
|
||||
public class UndoBatchStart : UndoAction, IUndoBatchStart
|
||||
{
|
||||
public String mName;
|
||||
public UndoBatchEnd mBatchEnd;
|
||||
|
||||
public int32 mBatchInsertIdx;
|
||||
public int32 mBatchSize = 0; // Don't close out until we add the end
|
||||
|
||||
public String Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return mName;
|
||||
}
|
||||
}
|
||||
|
||||
public this(String name)
|
||||
{
|
||||
mName = name;
|
||||
mBatchEnd = new UndoBatchEnd();
|
||||
mBatchEnd.mBatchStart = this;
|
||||
}
|
||||
|
||||
public IUndoBatchEnd BatchEnd
|
||||
{
|
||||
get
|
||||
{
|
||||
return mBatchEnd;
|
||||
}
|
||||
}
|
||||
|
||||
public override int32 GetCost()
|
||||
{
|
||||
return Math.Min(mBatchSize, 256); // Don't allow a large batch (ie: rename) to cause us to pull too much out of the undo buffer
|
||||
}
|
||||
}
|
||||
|
||||
public class UndoBatchEnd : UndoAction, IUndoBatchEnd
|
||||
{
|
||||
public IUndoBatchStart mBatchStart;
|
||||
public String Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return mBatchStart.Name;
|
||||
}
|
||||
}
|
||||
|
||||
public IUndoBatchStart BatchStart
|
||||
{
|
||||
get
|
||||
{
|
||||
return mBatchStart;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class UndoManager
|
||||
{
|
||||
List<UndoAction> mUndoList = new List<UndoAction>() ~ DeleteContainerAndItems!(_);
|
||||
int32 mUndoIdx = 0;
|
||||
int32 mMaxCost = 8192;
|
||||
int32 mCurCost = 0;
|
||||
bool mSkipNextMerge; // Don't merge after we do an undo or redo step
|
||||
int32 mFreezeDeletes;
|
||||
|
||||
public ~this()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
public void WithActions(Action<UndoAction> func)
|
||||
{
|
||||
for (var action in mUndoList)
|
||||
func(action);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
while (mUndoList.Count > 0)
|
||||
{
|
||||
var undoAction = mUndoList.PopBack();
|
||||
delete undoAction;
|
||||
}
|
||||
mUndoIdx = 0;
|
||||
mCurCost = 0;
|
||||
}
|
||||
|
||||
public void Add(UndoAction action, bool allowMerge = true)
|
||||
{
|
||||
if (mFreezeDeletes == 0)
|
||||
mCurCost += action.GetCost();
|
||||
if (action is IUndoBatchStart)
|
||||
{
|
||||
mFreezeDeletes++;
|
||||
if (var undoBatchStart = action as UndoBatchStart)
|
||||
{
|
||||
undoBatchStart.mBatchInsertIdx = GetActionCount();
|
||||
}
|
||||
}
|
||||
else if (action is IUndoBatchEnd)
|
||||
{
|
||||
mFreezeDeletes--;
|
||||
if (var undoBatchEnd = action as UndoBatchEnd)
|
||||
{
|
||||
if (var undoBatchStart = undoBatchEnd.mBatchStart as UndoBatchStart)
|
||||
{
|
||||
undoBatchStart.mBatchSize = GetActionCount() - undoBatchStart.mBatchInsertIdx;
|
||||
int32 cost = undoBatchStart.GetCost();
|
||||
Debug.Assert(cost >= 0);
|
||||
mCurCost += cost;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mUndoIdx < mUndoList.Count)
|
||||
{
|
||||
int32 batchDepth = 0;
|
||||
for (int checkIdx = mUndoIdx; checkIdx < mUndoList.Count; checkIdx++)
|
||||
{
|
||||
var checkAction = mUndoList[checkIdx];
|
||||
if (batchDepth == 0)
|
||||
mCurCost -= checkAction.GetCost();
|
||||
|
||||
if (checkAction is IUndoBatchStart)
|
||||
batchDepth++;
|
||||
else if (checkAction is IUndoBatchEnd)
|
||||
batchDepth--;
|
||||
|
||||
delete checkAction;
|
||||
}
|
||||
Debug.Assert(batchDepth == 0);
|
||||
|
||||
mUndoList.RemoveRange(mUndoIdx, mUndoList.Count - mUndoIdx);
|
||||
}
|
||||
|
||||
if ((allowMerge) && (!mSkipNextMerge))
|
||||
{
|
||||
UndoAction prevAction = GetLastUndoAction();
|
||||
if (prevAction is UndoBatchStart)
|
||||
{
|
||||
var undoBatchStart = (UndoBatchStart)prevAction;
|
||||
if (undoBatchStart.mBatchEnd == action)
|
||||
{
|
||||
// It's an empty batch!
|
||||
mUndoList.PopBack();
|
||||
delete prevAction;
|
||||
delete action;
|
||||
mUndoIdx--;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ((prevAction != null) && (prevAction.Merge(action)))
|
||||
{
|
||||
delete action;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mSkipNextMerge = false;
|
||||
mUndoList.Add(action);
|
||||
mUndoIdx++;
|
||||
|
||||
if ((mCurCost > mMaxCost) && (mFreezeDeletes == 0))
|
||||
{
|
||||
int32 wantCost = (int32)(mMaxCost * 0.8f); // Don't just remove one item at a time
|
||||
|
||||
int32 checkIdx = 0;
|
||||
int32 batchDepth = 0;
|
||||
while (((mCurCost > wantCost) || (batchDepth > 0)) &&
|
||||
(checkIdx < mUndoList.Count))
|
||||
{
|
||||
var checkAction = mUndoList[checkIdx];
|
||||
if (batchDepth == 0)
|
||||
mCurCost -= checkAction.GetCost();
|
||||
|
||||
if (checkAction is IUndoBatchStart)
|
||||
batchDepth++;
|
||||
else if (checkAction is IUndoBatchEnd)
|
||||
batchDepth--;
|
||||
|
||||
delete checkAction;
|
||||
checkIdx++;
|
||||
}
|
||||
|
||||
mUndoList.RemoveRange(0, checkIdx);
|
||||
mUndoIdx -= checkIdx;
|
||||
}
|
||||
}
|
||||
|
||||
public UndoAction GetLastUndoAction()
|
||||
{
|
||||
if (mUndoIdx == 0)
|
||||
return null;
|
||||
return mUndoList[mUndoIdx - 1];
|
||||
}
|
||||
|
||||
public bool Undo()
|
||||
{
|
||||
mSkipNextMerge = true;
|
||||
|
||||
if (mUndoIdx == 0)
|
||||
return false;
|
||||
|
||||
var undoAction = mUndoList[mUndoIdx - 1];
|
||||
if (IUndoBatchEnd undoBatchEnd = undoAction as IUndoBatchEnd)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (!undoAction.Undo())
|
||||
return false;
|
||||
if (mUndoIdx == 0)
|
||||
return false;
|
||||
mUndoIdx--;
|
||||
if ((undoAction == undoBatchEnd.BatchStart) || (mUndoIdx == 0))
|
||||
return true;
|
||||
undoAction = mUndoList[mUndoIdx - 1];
|
||||
}
|
||||
}
|
||||
|
||||
if (!undoAction.Undo())
|
||||
return false;
|
||||
|
||||
mUndoIdx--;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Redo()
|
||||
{
|
||||
mSkipNextMerge = true;
|
||||
|
||||
if (mUndoIdx >= mUndoList.Count)
|
||||
return false;
|
||||
|
||||
int32 startUndoIdx = mUndoIdx;
|
||||
var undoAction = mUndoList[mUndoIdx];
|
||||
if (undoAction is UndoBatchStart)
|
||||
{
|
||||
UndoBatchStart undoBatchStart = (UndoBatchStart)undoAction;
|
||||
while (true)
|
||||
{
|
||||
if (!undoAction.Redo())
|
||||
{
|
||||
mUndoIdx = startUndoIdx;
|
||||
return false;
|
||||
}
|
||||
if (mUndoIdx >= mUndoList.Count)
|
||||
return false;
|
||||
mUndoIdx++;
|
||||
if (undoAction == undoBatchStart.mBatchEnd)
|
||||
return true;
|
||||
undoAction = mUndoList[mUndoIdx];
|
||||
}
|
||||
}
|
||||
|
||||
if (!undoAction.Redo())
|
||||
return false;
|
||||
|
||||
mUndoIdx++;
|
||||
return true;
|
||||
}
|
||||
|
||||
public int32 GetActionCount()
|
||||
{
|
||||
return mUndoIdx;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue