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