diff --git a/HtmlTest/TestSuite.cpp b/HtmlTest/TestSuite.cpp
--- a/HtmlTest/TestSuite.cpp
+++ b/HtmlTest/TestSuite.cpp
@@ -1,631 +1,631 @@
#include "lgi/common/Lgi.h"
#include "lgi/common/Html.h"
#include "lgi/common/List.h"
#include "lgi/common/DateTime.h"
#include "lgi/common/ClipBoard.h"
#include "lgi/common/Scripting.h"
#include "lgi/common/Box.h"
#include "lgi/common/TextView3.h"
#include "lgi/common/ProgressDlg.h"
#include "lgi/common/Combo.h"
#include "lgi/common/Net.h"
#include "lgi/common/Http.h"
#include "lgi/common/Filter.h"
#include "lgi/common/NetTools.h"
#include "lgi/common/ImageComparison.h"
#include "lgi/common/OpenSSLSocket.h"
#include "lgi/common/Net.h"
#include "lgi/common/EmojiFont.h"
#include "lgi/common/Http.h"
#include "lgi/common/Menu.h"
#include "resdefs.h"
#define HAS_LOG_VIEW 0
#define HAS_IMAGE_LOADER 1
enum Controls
{
IDC_HTML = 100,
IDC_LOG,
IDC_LIST
};
class FileInf
{
public:
char *File;
LDateTime Date;
FileInf()
{
File = 0;
}
~FileInf()
{
DeleteArray(File);
}
};
int InfCmp(FileInf *a, FileInf *b, NativeInt d)
{
return -a->Date.Compare(&b->Date);
}
class HtmlItem : public LListItem
{
char *Base;
public:
HtmlItem(char *b, char *n)
{
Base = b;
SetText(n);
}
void OnMouseClick(LMouse &m)
{
if (m.IsContextMenu())
{
LSubMenu s;
s.AppendItem("Copy Path", 100);
m.ToScreen();
if (s.Float(GetList(), m.x, m.y, m.Left()) == 100)
{
LClipBoard c(GetList());
char Path[MAX_PATH_LEN];
LMakePath(Path, sizeof(Path), Base, GetText(0));
c.Text(Path);
}
}
}
};
class HtmlScriptContext :
#ifdef _GHTML2_H
public Html2::LHtml2,
#else
public Html1::LHtml,
#endif
public LScriptContext
{
LScriptEngine *Eng;
public:
static LHostFunc Methods[];
HtmlScriptContext(int Id, LDocumentEnv *Env) :
#ifdef _GHTML2_H
Html2::LHtml2
#else
Html1::LHtml
#endif
(Id, 0, 0, 100, 100, Env)
{
Eng = NULL;
LFont *f = new LFont;
if (f)
{
f->Face("Times New Roman");
f->PointSize(12); // Size '16' is about 12 pt. Maybe they mean 16px?
SetFont(f, true);
}
}
LHostFunc *GetCommands()
{
return Methods;
}
void SetEngine(LScriptEngine *Eng)
{
}
- char *GetIncludeFile(char *FileName)
+ LString GetIncludeFile(const char *FileName)
{
return NULL;
}
LAutoString GetDataFolder()
{
return LAutoString();
}
};
LHostFunc HtmlScriptContext::Methods[] =
{
LHostFunc(0, 0, 0),
};
class HtmlImageLoader : public LThread, public LMutex, public LCancel
{
LArray In;
public:
HtmlImageLoader() :
LThread("HtmlImageLoader.Thread"),
LMutex("HtmlImageLoader.Mutex")
{
Run();
}
~HtmlImageLoader()
{
Cancel(true);
while (!IsExited())
LSleep(1);
}
void Add(LDocumentEnv::LoadJob *j)
{
if (Lock(_FL))
{
In.Add(j);
Unlock();
}
}
LAutoPtr CreateSock(const char *Proto)
{
LAutoPtr s;
if (Proto && !_stricmp(Proto, "https"))
{
SslSocket *ss;
s.Reset(ss = new SslSocket);
ss->SetSslOnConnect(false);
}
else
s.Reset(new LSocket);
return s;
}
int Main()
{
while (!IsCancelled())
{
LAutoPtr j;
if (Lock(_FL))
{
if (In.Length())
{
j.Reset(In[0]);
In.DeleteAt(0, true);
}
Unlock();
}
LDocumentEnv::LoadJob *Job = dynamic_cast(j.Get());
if (Job)
{
LUri u(Job->Uri);
if (u.IsFile())
{
// Local document?
if (Job->pDC.Reset(GdcD->Load(Job->Uri)))
{
LDocumentEnv *e = Job->Env;
if (e)
{
// LgiTrace("Loaded '%s' as image %ix%i\n", u.Path, j->pDC->X(), j->pDC->Y());
e->OnDone(j);
}
}
}
else
{
LMemQueue p(1024);
LString Err;
auto r = LgiGetUri(this, &p, &Err, Job->Uri);
if (r)
{
uchar Hint[16];
p.Peek(Hint, sizeof(Hint));
auto Filter = LFilterFactory::New(u.sPath, FILTER_CAP_READ, Hint);
if (Filter)
{
LAutoPtr Img(new LMemDC);
LFilter::IoStatus Rd = Filter->ReadImage(Img, &p);
if (Rd == LFilter::IoSuccess)
{
Job->pDC = Img;
if (Job->Env)
{
// LgiTrace("Loaded '%s' as image %ix%i\n", u.Path, j->pDC->X(), j->pDC->Y());
Job->Env->OnDone(j);
}
else
{
LgiTrace("%s:%i - No env for '%s'\n", _FL, u.sPath.Get());
LAssert(0);
}
}
else LgiTrace("%s:%i - Failed to read '%s'\n", _FL, u.sPath.Get());
}
else LgiTrace("%s:%i - Failed to find filter for '%s'\n", _FL, u.sPath.Get());
}
else LgiTrace("%s:%i - Failed to get '%s'\n", _FL, Job->Uri.Get());
}
}
else LSleep(10);
}
return 0;
}
};
class AppWnd : public LWindow, public LDefaultDocumentEnv
{
LList *Lst;
HtmlScriptContext *Html;
LTextView3 *Text;
char Base[256];
LAutoPtr Script;
LAutoPtr Worker;
LAutoPtr Emoji;
LoadType GetContent(LoadJob *&j)
{
LUri u(j->Uri);
if (!u.sProtocol)
{
char p[MAX_PATH_LEN];
if (LMakePath(p, sizeof(p), Base, j->Uri) &&
LFileExists(p))
{
LString Ext = LGetExtension(p);
if (Ext.Equals("css") ||
Ext.Equals("html"))
{
LAutoPtr f(new LFile);
if (f && f->Open(p, O_READ))
{
j->Stream.Reset(f.Release());
return LoadImmediate;
}
}
else
{
j->pDC.Reset(GdcD->Load(p));
if (j->pDC)
return LoadImmediate;
}
return LoadError;
}
}
#if HAS_IMAGE_LOADER
if (!Worker)
Worker.Reset(new HtmlImageLoader);
Worker->Add(j);
j = NULL;
return LoadDeferred;
#else
return LoadNotImpl; // GDefaultDocumentEnv::GetContent(j);
#endif
}
public:
AppWnd()
{
Html = 0;
Lst = 0;
Text = NULL;
Base[0] = 0;
Emoji.Reset(new LEmojiFont());
Name("Html Test Suite");
SetQuitOnClose(true);
if (Attach(0))
{
Menu = new LMenu();
if (Menu)
{
Menu->Attach(this);
Menu->Load(this, "IDM_MENU");
}
LBox *s = new LBox;
if (s)
{
AddView(s);
s->AddView(Lst = new LList(IDC_LIST, 0, 0, 100, 100));
Lst->Sunken(false);
Lst->AddColumn("File", 400);
Lst->CssStyles("width: 200px;");
s->Value(200);
#if HAS_LOG_VIEW
LBox *vert = new LBox;
vert->SetVertical(true);
s->AddView(vert);
vert->AddView(Html = new HtmlScriptContext(IDC_HTML, this));
Html->SetCssStyle("height: 70%;");
vert->AddView(Text = new LTextView3(IDC_LOG, 0, 0, 100, 100));
LBox::Spacer *sp = s->GetSpacer(0);
if (sp)
{
sp->Colour.Rgb(64, 64, 64);
sp->SizePx = 2;
}
sp = vert->GetSpacer(0);
if (sp)
{
sp->Colour.Rgb(64, 64, 64);
sp->SizePx = 2;
}
#else
s->AddView(Html = new HtmlScriptContext(IDC_HTML, this));
#endif
Script.Reset(new LScriptEngine(this, Html, NULL));
if (Html)
Html->SetEnv(this);
#if HAS_IMAGE_LOADER
Html->SetLoadImages(true);
#endif
if (sprintf_s(Base, sizeof(Base), "%s", LGetExePath().Get()) > 0)
{
#if defined(WIN32)
if (stristr(Base, "Release") || stristr(Base, "Debug"))
LTrimDir(Base);
#endif
#if defined(MAC) && defined(__GTK_H__)
LMakePath(Base, sizeof(Base), Base, "../../../..");
#endif
List Files;
LDirectory *d = new LDirectory;
if (d)
{
LMakePath(Base, sizeof(Base), Base, "Files");
for (bool b = d->First(Base); b; b = d->Next())
{
if (!d->IsDir() && MatchStr("*.html", d->GetName()))
{
char p[256];
if (d->Path(p, sizeof(p)))
{
FileInf *f = new FileInf;
if (f)
{
f->File = NewStr(p);
f->Date.Set(d->GetLastWriteTime());
Files.Insert(f);
}
}
}
}
DeleteObj(d);
Files.Sort(InfCmp);
for (auto f: Files)
{
char *d = strrchr(f->File, DIR_CHAR);
if (d)
{
HtmlItem *i = new HtmlItem(Base, d + 1);
if (i)
Lst->Insert(i);
}
}
Files.DeleteObjects();
}
}
}
LRect r(0, 0, 1200, 800);
SetPos(r);
MoveToCenter();
AttachChildren();
Visible(true);
OnNotify(FindControl(IDC_LIST), LNotifyItemSelect);
}
else LExitApp();
}
~AppWnd()
{
}
int OnCommand(int Cmd, int Event, OsView Wnd)
{
switch (Cmd)
{
case IDM_SAVE_IMGS:
{
LPoint PageSize(1000, 2000);
LProgressDlg Prog(this, true);
Prog.SetDescription("Scanning for HTML...");
char p[MAX_PATH_LEN];
LGetSystemPath(LSP_APP_INSTALL, p, sizeof(p));
LArray Ext;
LArray Files;
Ext.Add("*.html");
Ext.Add("*.htm");
if (LRecursiveFileSearch(p, &Ext, &Files))
{
LDateTime Now;
Now.SetNow();
char OutPath[MAX_PATH_LEN], NowStr[32];
sprintf_s( NowStr, sizeof(NowStr),
"%.4i-%.2i-%.2i_%.2i-%.2i-%.2i",
Now.Year(), Now.Month(), Now.Day(),
Now.Hours(), Now.Minutes(), Now.Seconds());
LMakePath(OutPath, sizeof(OutPath), p, "Output");
if (!LDirExists(OutPath))
FileDev->CreateFolder(OutPath);
LMakePath(OutPath, sizeof(OutPath), OutPath, NowStr);
if (!LDirExists(OutPath))
FileDev->CreateFolder(OutPath);
Prog.SetDescription("Saving renders...");
Prog.SetRange(Files.Length());
for (int i=0; iSave(p, &Screen))
LAssert(0);
Prog.Value(i);
LYield();
}
Files.DeleteArrays();
}
break;
}
case IDM_COMPARE_IMAGES:
{
char p[MAX_PATH_LEN];
LGetSystemPath(LSP_APP_INSTALL, p, sizeof(p));
LArray Ext;
LArray Files;
Ext.Add("*.png");
// if (LRecursiveFileSearch(p, &Ext, &Files))
char OutPath[MAX_PATH_LEN];
LMakePath(OutPath, sizeof(OutPath), p, "Output");
if (!LDirExists(OutPath))
{
LgiMsg(this, "No output render folder.", "Html Test Suite");
break;
}
new ImageCompareDlg(this, OutPath);
break;
}
}
return 0;
}
int OnNotify(LViewI *c, LNotification n)
{
switch (c->GetId())
{
case IDC_LIST:
{
if (n.Type == LNotifyItemSelect)
{
LListItem *s = Lst->GetSelected();
if (s)
{
char p[256];
LMakePath(p, sizeof(p), Base, s->GetText(0));
if (LFileExists(p))
{
char *h = LReadTextFile(p);
if (h)
{
if (Html)
Html->Name(h);
DeleteArray(h);
}
}
}
}
break;
}
case IDC_HTML:
{
switch (n.Type)
{
case LNotifySelectionChanged:
{
if (Text)
{
LAutoString s(Html->GetSelection());
if (s)
Text->Name(s);
else
Text->Name("(null)");
}
break;
}
default:
break;
}
break;
}
}
return 0;
}
bool OnCompileScript(LDocView *Parent, char *Script, const char *Language, const char *MimeType)
{
// return Script->Compile(Code, true);
return false;
}
bool OnExecuteScript(LDocView *Parent, char *Script)
{
return false; // Script->RunTemporary(Code);
}
};
void FontSz()
{
for (int i=6; i<32; i++)
{
LFont f;
if (f.Create("verdana", LCss::Len(LCss::LenPx, i)))
{
double a = (double) f.GetHeight() / f.Ascent();
LgiTrace("%i: %i, ascent=%f, a=%f\n", i, f.GetHeight(), f.Ascent(), a);
}
}
}
int LgiMain(OsAppArguments &AppArgs)
{
LApp a(AppArgs, "HtmlTestSuite");
if (a.IsOk())
{
//FontSz();
a.AppWnd = new AppWnd;
a.Run();
}
return 0;
}
diff --git a/include/lgi/common/HashTable.h b/include/lgi/common/HashTable.h
--- a/include/lgi/common/HashTable.h
+++ b/include/lgi/common/HashTable.h
@@ -1,792 +1,792 @@
/*
More modern take on the GHashTbl I had been using for a while.
Moved the key management into a parameter class. All the key pooling
is also now managed by the param class rather than the hash table itself.
*/
#ifndef _LHashTbl_H_
#define _LHashTbl_H_
#include
#include "lgi/common/Mem.h"
#include "lgi/common/Array.h"
#include "lgi/common/LgiString.h"
#ifndef LHASHTBL_MAX_SIZE
#define LHASHTBL_MAX_SIZE (64 << 10)
#endif
#define HASH_TABLE_SHRINK_THRESHOLD 15
#define HASH_TABLE_GROW_THRESHOLD 50
template
class IntKey
{
public:
typedef T Type;
T NullKey;
IntKey()
{
NullKey = DefaultNull;
}
void EmptyKeys() {}
uint32_t Hash(T k) const { return (uint32_t)k; }
T CopyKey(T a) { return a; }
size_t SizeKey(T a) { return sizeof(a); }
void FreeKey(T &a) { a = NullKey; }
bool CmpKey(T a, T b) const
{
return a == b;
}
size_t TotalSize() { return 0; }
};
template
class PtrKey
{
public:
typedef T Type;
T NullKey;
PtrKey()
{
NullKey = DefaultNull;
}
void EmptyKeys() {}
uint32_t Hash(T k) const { return (uint32_t)(((size_t)k)/31); }
T CopyKey(T a) { return a; }
size_t SizeKey(T a) { return sizeof(a); }
void FreeKey(T &a) { a = NullKey; }
bool CmpKey(T a, T b) const
{
return a == b;
}
size_t TotalSize() { return 0; }
};
template
class StrKey
{
public:
typedef T *Type;
T *NullKey;
StrKey()
{
NullKey = DefaultNull;
}
void EmptyKeys() {}
uint32_t Hash(T *k) const { return LHash(k, Strlen(k), CaseSen); }
T *CopyKey(T *a) { return Strdup(a); }
size_t SizeKey(T *a) { return (Strlen(a)+1)*sizeof(*a); }
void FreeKey(T *&a) { if (a) delete [] a; a = NullKey; }
bool CmpKey(T *a, T *b) const { return !(CaseSen ? Strcmp(a, b) : Stricmp(a, b)); }
size_t TotalSize() { return 0; }
};
template
class KeyPool
{
protected:
struct Buf : public LArray
{
size_t Used;
Buf(size_t Sz = 0) { this->Length(Sz); }
size_t Free() { return this->Length() - Used; }
};
LArray Mem;
Buf *GetMem(size_t Sz)
{
if (!Mem.Length() || Mem.Last().Free() < Sz)
Mem.New().Length(PoolSize);
return Mem.Last().Free() >= Sz ? &Mem.Last() : NULL;
}
public:
const int DefaultPoolSize = (64 << 10) / sizeof(T);
int PoolSize;
KeyPool()
{
PoolSize = BlockSize ? BlockSize : DefaultPoolSize;
}
void EmptyKeys()
{
Mem.Length(0);
}
size_t TotalSize()
{
size_t s = 0;
for (auto &b : Mem)
s += sizeof(Buf) + (b.Length() * sizeof(T));
return s;
}
};
template
class ConstStrKey
{
public:
typedef const T *Type;
const T *NullKey;
ConstStrKey()
{
NullKey = DefaultNull;
}
void EmptyKeys() {}
uint32_t Hash(const T *k) const { return LHash(k, Strlen(k), CaseSen); }
T *CopyKey(const T *a) { return Strdup(a); }
size_t SizeKey(const T *a) { return (Strlen(a)+1)*sizeof(*a); }
void FreeKey(const T *&a) { if (a) delete [] a; a = NullKey; }
bool CmpKey(const T *a, const T *b) const { return !(CaseSen ? Strcmp(a, b) : Stricmp(a, b)); }
size_t TotalSize() { return 0; }
};
template
class StrKeyPool : public KeyPool
{
public:
typedef T *Type;
using Buf = typename KeyPool::Buf;
T *NullKey;
StrKeyPool()
{
NullKey = DefaultNull;
}
- uint32_t Hash(T *k) { return LHash(k, Strlen(k), CaseSen); }
+ uint32_t Hash(T *k) const { return LHash(k, Strlen(k), CaseSen); }
size_t SizeKey(T *a) { return (Strlen(a)+1)*sizeof(*a); }
bool CmpKey(T *a, T *b) const { return !(CaseSen ? Strcmp(a, b) : Stricmp(a, b)); }
T *CopyKey(T *a)
{
size_t Sz = Strlen(a) + 1;
Buf *m = this->GetMem(Sz);
if (!m) return NullKey;
T *r = m->AddressOf(m->Used);
memcpy(r, a, Sz*sizeof(*a));
m->Used += Sz;
return r;
}
void FreeKey(T *&a)
{
// Do nothing... memory is own by KeyPool
a = NullKey;
}
};
template
class ConstStrKeyPool : public KeyPool
{
public:
typedef const T *Type;
using Buf = typename KeyPool::Buf;
const T *NullKey;
ConstStrKeyPool()
{
NullKey = DefaultNull;
}
uint32_t Hash(const T *k) const { return LHash(k, Strlen(k), CaseSen); }
size_t SizeKey(const T *a) { return (Strlen(a)+1)*sizeof(*a); }
bool CmpKey(const T *a, const T *b) const { return !(CaseSen ? Strcmp(a, b) : Stricmp(a, b)); }
const T *CopyKey(const T *a)
{
size_t Sz = Strlen(a) + 1;
Buf *m = this->GetMem(Sz);
if (!m) return NullKey;
T *r = m->AddressOf(m->Used);
memcpy(r, a, Sz*sizeof(*a));
m->Used += Sz;
return r;
}
void FreeKey(const T *&a)
{
// Do nothing... memory is own by KeyPool
a = NullKey;
}
};
/// General hash table container for O(1) access to table data.
template
class LHashTbl : public KeyTrait
{
public:
typedef typename KeyTrait::Type Key;
typedef LHashTbl HashTable;
const int DefaultSize = 256;
struct Pair
{
Key key;
Value value;
};
protected:
Value NullValue;
size_t Used;
size_t Size;
size_t MaxSize;
int Version; // This changes every time 'Table' is resized.
// It's used to invalidate iterators.
Pair *Table;
int Percent()
{
return (int) (Used * 100 / Size);
}
bool GetEntry(const Key k, ssize_t &Index, bool Debug = false) const
{
if (k != this->NullKey && Table)
{
uint32_t h = this->Hash(k);
for (size_t i=0; iNullKey)
return false;
if (this->CmpKey(Table[Index].key, k))
return true;
}
}
return false;
}
bool Between(ssize_t Val, ssize_t Min, ssize_t Max)
{
if (Min <= Max)
{
// Not wrapped
return Val >= Min && Val <= Max;
}
else
{
// Wrapped
return Val <= Max || Val >= Min;
}
}
void InitializeTable(Pair *e, ssize_t len)
{
if (!e || len < 1) return;
while (len--)
{
e->key = this->NullKey;
e->value = NullValue;
e++;
}
}
public:
constexpr static size_t Unlimited = 0;
/// Constructs the hash table
LHashTbl
(
/// Sets the initial table size. Should be 2x your data set.
size_t size = 0,
/// The default empty value
Value nullvalue = (Value)0
)
{
Size = size;
NullValue = nullvalue;
Used = 0;
Version = 0;
MaxSize = LHASHTBL_MAX_SIZE;
// LAssert(Size <= MaxSize);
if ((Table = new Pair[Size]))
{
InitializeTable(Table, Size);
}
}
LHashTbl(const HashTable &init)
{
Size = init.Size;
NullValue = init.NullValue;
Used = 0;
Version = 0;
MaxSize = LHASHTBL_MAX_SIZE;
if ((Table = new Pair[Size]))
{
for (size_t i=0; iNullKey;
}
*this = init;
}
}
/// Deletes the hash table removing all contents from memory
virtual ~LHashTbl()
{
Empty();
DeleteArray(Table);
}
Key GetNullKey()
{
return this->NullKey;
}
/// Copy operator
HashTable &operator =(const HashTable &c)
{
if (IsOk() && c.IsOk())
{
Empty();
this->NullKey = c.NullKey;
NullValue = c.NullValue;
size_t Added = 0;
for (size_t i=0; i MaxSize)
{
LAssert(!"Max size reached.");
return false;
}
Size = NewSize;
Table = new Pair[Size];
if (Table)
{
size_t i;
InitializeTable(Table, Size);
for (i=0; iNullKey)
{
if (!Add(OldTable[i].key, OldTable[i].value))
{
LAssert(0);
}
this->FreeKey(OldTable[i].key);
}
}
Version++;
Status = true;
}
else
{
LAssert(Table != 0);
Table = OldTable;
Size = OldSize;
return false;
}
DeleteArray(OldTable);
}
return Status;
}
/// Returns true if the object appears to be valid
bool IsOk() const
{
bool Status =
#ifndef __llvm__
this != 0 &&
#endif
Table != 0;
if (!Status)
{
#ifndef LGI_STATIC
LgiTrace("%s:%i - this=%p Table=%p Used=%i Size=%i\n", _FL, this, Table, Used, Size);
#endif
LAssert(0);
}
return Status;
}
/// Gets the number of entries used
size_t Length()
{
return IsOk() ? Used : 0;
}
/// Adds a value under a given key
bool Add
(
/// The key to insert the value under
Key k,
/// The value to insert
Value v
)
{
if (!Size && !SetSize(DefaultSize))
return false;
if (IsOk() &&
k == this->NullKey &&
v == NullValue)
{
LAssert(!"Adding NULL key or value.");
return false;
}
uint32_t h = this->Hash(k);
ssize_t Index = -1;
for (size_t i=0; iNullKey
||
this->CmpKey(Table[idx].key, k)
)
{
Index = idx;
break;
}
}
if (Index >= 0)
{
if (Table[Index].key == this->NullKey)
{
Table[Index].key = this->CopyKey(k);
Used++;
}
Table[Index].value = v;
if (Percent() > HASH_TABLE_GROW_THRESHOLD)
{
if (!SetSize(Size << 1))
return false;
}
return true;
}
LAssert(!"Couldn't alloc space.");
return false;
}
/// Deletes a value at 'key'
bool Delete
(
/// The key of the value to delete
Key k,
/// Turns off resizing, in case your iterating over the hash table,
/// where resizing would invalidate the iterators.
bool NoResize = false
)
{
ssize_t Index = -1;
if (GetEntry(k, Index))
{
// Delete the entry
this->FreeKey(Table[Index].key);
Table[Index].value = NullValue;
Used--;
// Bubble down any entries above the hole
ssize_t Hole = Index;
for (ssize_t i = (Index + 1) % Size; i != Index; i = (i + 1) % Size)
{
if (Table[i].key != this->NullKey)
{
uint32_t Hsh = this->Hash(Table[i].key);
uint32_t HashIndex = Hsh % Size;
if (HashIndex != i && Between(Hole, HashIndex, i))
{
// Do bubble
if (Table[Hole].key != this->NullKey)
{
LAssert(0);
}
memmove(Table + Hole, Table + i, sizeof(Table[i]));
InitializeTable(Table + i, 1);
Hole = i;
}
}
else
{
// Reached the end of entries that could have bubbled
break;
}
}
// Check for auto-shrink limit
if (!NoResize &&
Percent() < HASH_TABLE_SHRINK_THRESHOLD)
{
if (!SetSize(Size >> 1))
return false;
}
return true;
}
else
{
GetEntry(k, Index, true);
}
return false;
}
/// Returns the value at 'key'
Value Find(const Key k) const
{
ssize_t Index = -1;
if (IsOk() && GetEntry(k, Index))
{
return Table[Index].value;
}
return NullValue;
}
/// Returns the Key at 'val'
Key FindKey(const Value val)
{
if (IsOk())
{
Pair *c = Table;
Pair *e = Table + Size;
while (c < e)
{
if (c->value == val)
{
return c->key;
}
c++;
}
}
return this->NullKey;
}
/// Removes all key/value pairs from memory
void Empty()
{
if (!IsOk())
return;
for (size_t i=0; iNullKey)
{
this->FreeKey(Table[i].key);
LAssert(Table[i].key == this->NullKey);
}
Table[i].value = NullValue;
}
Used = 0;
this->EmptyKeys();
}
/// Returns the amount of memory in use by the hash table.
int64 Sizeof()
{
int64 Sz = sizeof(*this);
Sz += Sz * sizeof(Pair);
int64 KeySize = 0;
size_t Total = KeyTrait::TotalSize();
if (Total)
{
KeySize += Total;
}
else
{
int Keys = 0;
for (size_t i=0; iNullKey)
{
Keys++;
KeySize += this->SizeKey(Table[i].key);
}
}
}
return Sz + KeySize;
}
/// Deletes values as objects
void DeleteObjects()
{
for (size_t i=0; iNullKey)
this->FreeKey(Table[i].key);
if (Table[i].value != NullValue)
DeleteObj(Table[i].value);
}
Used = 0;
}
/// Deletes values as arrays
void DeleteArrays()
{
for (size_t i=0; iNullKey)
this->FreeKey(Table[i].key);
if (Table[i].value != NullValue)
DeleteArray(Table[i].value);
}
Used = 0;
}
/// Swaps the objects
void Swap(LHashTbl &h)
{
LSwap(this->NullKey, h.NullKey);
LSwap(NullValue, h.NullValue);
LSwap(Used, h.Used);
LSwap(Size, h.Size);
LSwap(MaxSize, h.MaxSize);
LSwap(Version, h.Version);
LSwap(Table, h.Table);
}
struct PairIterator
{
LHashTbl *t;
ssize_t Idx;
int Version;
public:
PairIterator(LHashTbl *tbl, ssize_t i)
{
t = tbl;
Version = t->Version;
Idx = i;
if (Idx < 0)
Next();
}
bool operator !=(const PairIterator &it) const
{
bool Eq = t == it.t &&
Idx == it.Idx;
return !Eq;
}
PairIterator &Next()
{
if (t->IsOk())
{
if (Version != t->Version)
{
#ifndef LGI_UNIT_TESTS
LAssert(!"Iterator invalidated");
#endif
*this = t->end();
}
else
{
while (++Idx < (ssize_t)t->Size)
{
if (t->Table[Idx].key != t->NullKey)
break;
}
}
}
return *this;
}
PairIterator &operator ++() { return Next(); }
PairIterator &operator ++(int) { return Next(); }
Pair &operator *()
{
LAssert( Idx >= 0 &&
Idx < (ssize_t)t->Size &&
t->Table[Idx].key != t->NullKey);
return t->Table[Idx];
}
};
PairIterator begin()
{
return PairIterator(this, -1);
}
PairIterator end()
{
return PairIterator(this, Size);
}
};
#endif