mirror of
https://github.com/Leonmmcoset/CMLeonOS.git
synced 2026-03-03 11:37:01 +00:00
582 lines
13 KiB
C#
582 lines
13 KiB
C#
|
|
// #define DEBUG_DUMMY_TVALUE_MODIFY
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace UniLua
|
|
{
|
|
using ULDebug = UniLua.Tools.ULDebug;
|
|
|
|
public class LuaTable {
|
|
public LuaTable MetaTable;
|
|
public uint NoTagMethodFlags;
|
|
|
|
public LuaTable(LuaState l) {
|
|
InitLuaTable(l);
|
|
}
|
|
|
|
~LuaTable()
|
|
{
|
|
Recycle();
|
|
}
|
|
|
|
public StkId Get(ref TValue key)
|
|
{
|
|
if(key.Tt == (int)LuaType.LUA_TNIL) { return TheNilValue; }
|
|
|
|
if(IsPositiveInteger(ref key))
|
|
{ return GetInt((int)key.NValue); }
|
|
|
|
if(key.Tt == (int)LuaType.LUA_TSTRING)
|
|
{ return GetStr(key.SValue()); }
|
|
|
|
var h = key.GetHashCode();
|
|
for(var node = GetHashNode(h); node != null; node = node.Next) {
|
|
if(node.Key.V == key) {
|
|
{ return node.Val; }
|
|
}
|
|
}
|
|
|
|
return TheNilValue;
|
|
}
|
|
|
|
public StkId GetInt(int key)
|
|
{
|
|
if(0 < key && key-1 < ArrayPart.Length)
|
|
{ return ArrayPart[key-1]; }
|
|
|
|
var k = new TValue();
|
|
k.SetNValue(key);
|
|
for(var node = GetHashNode(ref k); node != null; node = node.Next) {
|
|
if(node.Key.V.TtIsNumber() && node.Key.V.NValue == (double)key) {
|
|
return node.Val;
|
|
}
|
|
}
|
|
|
|
return TheNilValue;
|
|
}
|
|
|
|
public StkId GetStr(string key)
|
|
{
|
|
var h = key.GetHashCode();
|
|
for(var node = GetHashNode(h); node != null; node = node.Next) {
|
|
if(node.Key.V.TtIsString() && node.Key.V.SValue() == key)
|
|
{ return node.Val; }
|
|
}
|
|
|
|
return TheNilValue;
|
|
}
|
|
|
|
public void Set(ref TValue key, ref TValue val)
|
|
{
|
|
var cell = Get(ref key);
|
|
if(cell == TheNilValue) {
|
|
cell = NewTableKey(ref key);
|
|
}
|
|
cell.V.SetObj(ref val);
|
|
}
|
|
|
|
public void SetInt(int key, ref TValue val)
|
|
{
|
|
var cell = GetInt(key);
|
|
if(cell == TheNilValue) {
|
|
var k = new TValue();
|
|
k.SetNValue(key);
|
|
cell = NewTableKey(ref k);
|
|
}
|
|
cell.V.SetObj(ref val);
|
|
// Console.WriteLine(string.Format("---------------- SetInt {0} -> {1}", key, val));
|
|
// DumpParts();
|
|
}
|
|
|
|
/*
|
|
** returns the index of a `key' for table traversals. First goes all
|
|
** elements in the array part, then elements in the hash part. The
|
|
** beginning of a traversal is signaled by -1.
|
|
*/
|
|
private int FindIndex(StkId key)
|
|
{
|
|
if(key.V.TtIsNil())
|
|
{ return -1; }
|
|
|
|
// is `key' inside array part?
|
|
int i = ArrayIndex(ref key.V);
|
|
if(0 < i && i <= ArrayPart.Length)
|
|
{ return i-1; }
|
|
|
|
var n = GetHashNode(ref key.V);
|
|
// check whether `key' is somewhere in the chain
|
|
for(;;) {
|
|
if(L.V_RawEqualObj(ref n.Key.V, ref key.V))
|
|
{ return ArrayPart.Length + n.Index; }
|
|
n = n.Next;
|
|
|
|
// key not found
|
|
if(n == null) { L.G_RunError("invalid key to 'next'"); }
|
|
}
|
|
}
|
|
|
|
public bool Next(StkId key, StkId val)
|
|
{
|
|
// find original element
|
|
int i = FindIndex(key);
|
|
|
|
// try first array part
|
|
for(i++; i<ArrayPart.Length; ++i) {
|
|
if(!ArrayPart[i].V.TtIsNil()) {
|
|
key.V.SetNValue(i+1);
|
|
val.V.SetObj(ref ArrayPart[i].V);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// then hash part
|
|
for(i-=ArrayPart.Length; i<HashPart.Length; ++i) {
|
|
if(!HashPart[i].Val.V.TtIsNil()) {
|
|
key.V.SetObj(ref HashPart[i].Key.V);
|
|
val.V.SetObj(ref HashPart[i].Val.V);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public int Length
|
|
{ get {
|
|
uint j = (uint)ArrayPart.Length;
|
|
if(j > 0 && ArrayPart[j-1].V.TtIsNil()) {
|
|
/* there is a boundary in the array part: (binary) search for it */
|
|
uint i = 0;
|
|
while(j - i > 1) {
|
|
uint m = (i+j)/2;
|
|
if(ArrayPart[m-1].V.TtIsNil()) { j = m; }
|
|
else { i = m; }
|
|
}
|
|
return (int)i;
|
|
}
|
|
/* else must find a boundary in hash part */
|
|
else if(HashPart == DummyHashPart)
|
|
return (int)j;
|
|
else return UnboundSearch(j);
|
|
} }
|
|
|
|
public void Resize(int nasize, int nhsize)
|
|
{
|
|
int oasize = ArrayPart.Length;
|
|
var oldHashPart = HashPart;
|
|
if(nasize > oasize) // array part must grow?
|
|
SetArraryVector(nasize);
|
|
|
|
// create new hash part with appropriate size
|
|
SetNodeVector(nhsize);
|
|
|
|
// array part must shrink?
|
|
if(nasize < oasize) {
|
|
var oldArrayPart = ArrayPart;
|
|
ArrayPart = DummyArrayPart;
|
|
// re-insert elements from vanishing slice
|
|
for(int i=nasize; i<oasize; ++i) {
|
|
if(!oldArrayPart[i].V.TtIsNil())
|
|
{ SetInt(i+1, ref oldArrayPart[i].V); }
|
|
}
|
|
// shrink array
|
|
var newArrayPart = new StkId[nasize];
|
|
for(int i=0; i<nasize; ++i) {
|
|
newArrayPart[i] = oldArrayPart[i];
|
|
}
|
|
ArrayPart = newArrayPart;
|
|
}
|
|
|
|
// re-insert elements from hash part
|
|
for(int i=0; i<oldHashPart.Length; ++i) {
|
|
var node = oldHashPart[i];
|
|
if(!node.Val.V.TtIsNil()) {
|
|
Set(ref node.Key.V, ref node.Val.V);
|
|
}
|
|
}
|
|
|
|
if (oldHashPart != DummyHashPart)
|
|
RecycleHNode(oldHashPart);
|
|
}
|
|
|
|
//-----------------------------------------
|
|
//
|
|
// **** PRIVATE below ****
|
|
//
|
|
//-----------------------------------------
|
|
|
|
private class HNode
|
|
{
|
|
public int Index;
|
|
public StkId Key;
|
|
public StkId Val;
|
|
public HNode Next;
|
|
|
|
public void CopyFrom(HNode o)
|
|
{
|
|
Key.V.SetObj(ref o.Key.V);
|
|
Val.V.SetObj(ref o.Val.V);
|
|
Next = o.Next;
|
|
}
|
|
}
|
|
|
|
private LuaState L;
|
|
|
|
private StkId[] ArrayPart;
|
|
private HNode[] HashPart;
|
|
private int LastFree;
|
|
|
|
private static StkId TheNilValue;
|
|
private static StkId[] DummyArrayPart;
|
|
private static HNode DummyNode;
|
|
private static HNode[] DummyHashPart;
|
|
|
|
private const int MAXBITS = 30;
|
|
private const int MAXASIZE = (1 << MAXBITS);
|
|
|
|
static LuaTable()
|
|
{
|
|
TheNilValue = new StkId();
|
|
TheNilValue.V.SetNilValue();
|
|
#if DEBUG_DUMMY_TVALUE_MODIFY
|
|
TheNilValue.V.Lock_ = true;
|
|
#endif
|
|
|
|
DummyArrayPart = new StkId[0];
|
|
|
|
DummyNode = new HNode();
|
|
DummyNode.Key = TheNilValue;
|
|
DummyNode.Val = TheNilValue;
|
|
DummyNode.Next = null;
|
|
|
|
DummyHashPart = new HNode[1];
|
|
DummyHashPart[0] = DummyNode;
|
|
DummyHashPart[0].Index = 0;
|
|
}
|
|
|
|
#region Small Object Cache
|
|
private static HNode CacheHead = null;
|
|
private static object CacheHeadLock = new Object();
|
|
|
|
private void Recycle()
|
|
{
|
|
if (HashPart != null && HashPart != DummyHashPart)
|
|
{
|
|
RecycleHNode(HashPart);
|
|
HashPart = null;
|
|
}
|
|
}
|
|
|
|
private void RecycleHNode(HNode[] garbage)
|
|
{
|
|
if (garbage == null || garbage.Length == 0)
|
|
return;
|
|
|
|
for (int i = 0; i < garbage.Length-1; i++)
|
|
{
|
|
garbage[i].Next = garbage[i + 1];
|
|
}
|
|
|
|
lock(CacheHeadLock) {
|
|
garbage[garbage.Length - 1].Next = CacheHead;
|
|
CacheHead = garbage[0];
|
|
}
|
|
}
|
|
|
|
private HNode NewHNode()
|
|
{
|
|
HNode ret;
|
|
if (CacheHead == null)
|
|
{
|
|
ret = new HNode();
|
|
ret.Key = new StkId();
|
|
ret.Val = new StkId();
|
|
}
|
|
else
|
|
{
|
|
lock(CacheHeadLock) {
|
|
ret = CacheHead;
|
|
CacheHead = CacheHead.Next;
|
|
}
|
|
ret.Next = null;
|
|
ret.Index = 0;
|
|
ret.Key.V.SetNilValue();
|
|
ret.Val.V.SetNilValue();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endregion
|
|
|
|
private void InitLuaTable(LuaState lua)
|
|
{
|
|
L = lua;
|
|
ArrayPart = DummyArrayPart;
|
|
SetNodeVector(0);
|
|
}
|
|
|
|
private bool IsPositiveInteger(ref TValue v)
|
|
{
|
|
return (v.TtIsNumber() && v.NValue > 0 && (v.NValue % 1) == 0 && v.NValue <= int.MaxValue); //fix large number key bug
|
|
}
|
|
|
|
private HNode GetHashNode(int hashcode)
|
|
{
|
|
uint n = (uint)hashcode;
|
|
return HashPart[n % HashPart.Length];
|
|
}
|
|
|
|
private HNode GetHashNode(ref TValue v)
|
|
{
|
|
if(IsPositiveInteger(ref v)) { return GetHashNode((int)v.NValue); }
|
|
|
|
if(v.TtIsString()) { return GetHashNode(v.SValue().GetHashCode()); }
|
|
|
|
return GetHashNode(v.GetHashCode());
|
|
}
|
|
|
|
private void SetArraryVector(int size)
|
|
{
|
|
Utl.Assert(size >= ArrayPart.Length);
|
|
|
|
var newArrayPart = new StkId[size];
|
|
int i = 0;
|
|
for( ; i<ArrayPart.Length; ++i) {
|
|
newArrayPart[i] = ArrayPart[i];
|
|
}
|
|
for( ; i<size; ++i) {
|
|
newArrayPart[i] = new StkId();
|
|
newArrayPart[i].V.SetNilValue();
|
|
}
|
|
ArrayPart = newArrayPart;
|
|
}
|
|
|
|
private void SetNodeVector(int size)
|
|
{
|
|
if(size == 0) {
|
|
HashPart = DummyHashPart;
|
|
LastFree = size;
|
|
return;
|
|
}
|
|
|
|
int lsize = CeilLog2(size);
|
|
if(lsize > MAXBITS) { L.G_RunError("table overflow"); }
|
|
|
|
size = (1 << lsize);
|
|
HashPart = new HNode[size];
|
|
for(int i=0; i<size; ++i) {
|
|
HashPart[i] = NewHNode();
|
|
HashPart[i].Index = i;
|
|
}
|
|
LastFree = size;
|
|
}
|
|
|
|
private HNode GetFreePos()
|
|
{
|
|
while(LastFree > 0) {
|
|
var node = HashPart[--LastFree];
|
|
if(node.Key.V.TtIsNil()) { return node; }
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/*
|
|
** returns the index for `key' if `key' is an appropriate key to live in
|
|
** the array part of the table, -1 otherwise.
|
|
*/
|
|
private int ArrayIndex(ref TValue k)
|
|
{
|
|
if(IsPositiveInteger(ref k))
|
|
return (int)k.NValue;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
private static readonly byte[] Log2_ = new byte[] {
|
|
0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
|
|
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
|
|
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
|
|
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
|
|
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
|
|
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
|
|
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
|
|
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8
|
|
};
|
|
private int CeilLog2(int x)
|
|
{
|
|
Utl.Assert(x > 0);
|
|
int l = 0;
|
|
x--;
|
|
while(x >= 256) { l+=8; x>>=8; }
|
|
return l + Log2_[x];
|
|
}
|
|
|
|
private int CountInt(ref TValue key, ref int[] nums)
|
|
{
|
|
int k = ArrayIndex(ref key);
|
|
if(0 < k && k <= MAXASIZE) {
|
|
nums[CeilLog2(k)]++;
|
|
return 1;
|
|
}
|
|
else return 0;
|
|
}
|
|
|
|
private int NumUseArray(ref int[] nums)
|
|
{
|
|
int ause = 0;
|
|
int i = 1;
|
|
for(int lg=0, ttlg=1; lg<=MAXBITS; lg++, ttlg*=2) {
|
|
int lc = 0; // counter
|
|
int lim = ttlg;
|
|
if(lim > ArrayPart.Length) {
|
|
lim = ArrayPart.Length;
|
|
if(i > lim) { break; } // no more elements to count
|
|
}
|
|
|
|
// count elements in range (2^(lg-1), 2^lg]
|
|
for(; i<=lim; ++i) {
|
|
if(!ArrayPart[i-1].V.TtIsNil()) { lc++; }
|
|
}
|
|
nums[lg] += lc;
|
|
ause += lc;
|
|
}
|
|
return ause;
|
|
}
|
|
|
|
private int NumUseHash(ref int[] nums, ref int nasize)
|
|
{
|
|
int totaluse = 0;
|
|
int ause = 0;
|
|
int i = HashPart.Length;
|
|
while(i-- > 0) {
|
|
var n = HashPart[i];
|
|
if(!n.Val.V.TtIsNil()) {
|
|
ause += CountInt(ref n.Key.V, ref nums);
|
|
totaluse++;
|
|
}
|
|
}
|
|
nasize += ause;
|
|
return totaluse;
|
|
}
|
|
|
|
private int ComputeSizes(ref int[] nums, ref int nasize)
|
|
{
|
|
int a = 0;
|
|
int na = 0;
|
|
int n = 0;
|
|
for(int i=0, tti=1; tti/2<nasize; ++i, tti*=2) {
|
|
if(nums[i] > 0) {
|
|
a += nums[i];
|
|
if(a > tti/2) {
|
|
n = tti;
|
|
na = a;
|
|
}
|
|
}
|
|
if(a == nasize) { break; } // all elements already conted
|
|
}
|
|
nasize = n;
|
|
Utl.Assert(nasize/2 <= na && na <= nasize);
|
|
return na;
|
|
}
|
|
|
|
private static int[] Nums = new int[MAXBITS + 1];
|
|
private void Rehash(ref TValue k)
|
|
{
|
|
for(int i=0; i<=MAXBITS; ++i) { Nums[i] = 0; }
|
|
|
|
int nasize = NumUseArray(ref Nums);
|
|
int totaluse = nasize;
|
|
totaluse += NumUseHash(ref Nums, ref nasize);
|
|
nasize += CountInt(ref k, ref Nums);
|
|
totaluse++;
|
|
int na = ComputeSizes(ref Nums, ref nasize);
|
|
Resize(nasize, totaluse-na);
|
|
}
|
|
|
|
private void DumpParts()
|
|
{
|
|
Console.WriteLine("------------------ [DumpParts] enter -----------------------");
|
|
Console.WriteLine("<< Array Part >>");
|
|
for(var i=0; i<ArrayPart.Length; ++i) {
|
|
var n = ArrayPart[i];
|
|
Console.WriteLine(string.Format("i:{0} val:{1}", i, n.V));
|
|
}
|
|
Console.WriteLine("<< Hash Part >>");
|
|
for(var i=0; i<HashPart.Length; ++i) {
|
|
var n = HashPart[i];
|
|
var next = (n.Next == null) ? -1 : n.Next.Index;
|
|
Console.WriteLine(string.Format("i:{0} index:{1} key:{2} val:{3} next:{4}", i, n.Index, n.Key.V, n.Val.V, next));
|
|
}
|
|
Console.WriteLine("++++++++++++++++++ [DumpParts] leave +++++++++++++++++++++++");
|
|
}
|
|
|
|
private StkId NewTableKey(ref TValue k)
|
|
{
|
|
if(k.TtIsNil()) { L.G_RunError("table index is nil"); }
|
|
|
|
if(k.TtIsNumber() && System.Double.IsNaN(k.NValue))
|
|
{ L.G_RunError("table index is NaN"); }
|
|
|
|
var mp = GetHashNode(ref k);
|
|
|
|
// if main position is taken
|
|
if(!mp.Val.V.TtIsNil() || mp == DummyNode) {
|
|
var n = GetFreePos();
|
|
if(n == null) {
|
|
Rehash(ref k);
|
|
var cell = Get(ref k);
|
|
if(cell != TheNilValue) { return cell; }
|
|
return NewTableKey(ref k);
|
|
}
|
|
|
|
Utl.Assert(n != DummyNode);
|
|
var othern = GetHashNode(ref mp.Key.V);
|
|
// is colliding node out of its main position?
|
|
if(othern != mp) {
|
|
while(othern.Next != mp) { othern = othern.Next; }
|
|
othern.Next = n;
|
|
n.CopyFrom(mp);
|
|
mp.Next = null;
|
|
mp.Val.V.SetNilValue();
|
|
}
|
|
// colliding node is in its own main position
|
|
else {
|
|
n.Next = mp.Next;
|
|
mp.Next = n;
|
|
mp = n;
|
|
}
|
|
}
|
|
|
|
mp.Key.V.SetObj(ref k);
|
|
Utl.Assert(mp.Val.V.TtIsNil());
|
|
return mp.Val;
|
|
}
|
|
|
|
private int UnboundSearch(uint j)
|
|
{
|
|
uint i = j;
|
|
j++;
|
|
while(!GetInt((int)j).V.TtIsNil()) {
|
|
i = j;
|
|
j *= 2;
|
|
|
|
// overflow?
|
|
if(j > LuaLimits.MAX_INT) {
|
|
/* table was built with bad purposes: resort to linear search */
|
|
i = 1;
|
|
while(!GetInt((int)i).V.TtIsNil()) { i++; }
|
|
return (int)(i-1);
|
|
}
|
|
}
|
|
/* now do a binary search between them */
|
|
while(j - i > 1) {
|
|
uint m = (i + j) / 2;
|
|
if(GetInt((int)m).V.TtIsNil()) { j = m; }
|
|
else { i = m; }
|
|
}
|
|
return (int)i;
|
|
}
|
|
}
|
|
}
|