diff --git a/cinera/cinera.c b/cinera/cinera.c index ee17ed4..58cba84 100644 --- a/cinera/cinera.c +++ b/cinera/cinera.c @@ -23,7 +23,7 @@ typedef struct version CINERA_APP_VERSION = { .Major = 0, .Minor = 10, - .Patch = 22 + .Patch = 23 }; #define __USE_XOPEN2K8 // NOTE(matt): O_NOFOLLOW @@ -85,6 +85,7 @@ typedef uint64_t bool; #define DEBUG_MEMORY_LEAKAGE_LOOPED 0 #define DEBUG_PRINT_FUNCTION_NAMES 0 #define DEBUG_PROJECT_INDICES 0 +#define DEBUG_CLASH_RESOLVER 0 #define DEBUG 0 #define DEBUG_MEM 0 @@ -94,13 +95,50 @@ typedef uint64_t bool; typedef struct { - char DesiredMessage[512]; // TODO(matt): memory_book or other growable buffer - char LastMessage[512]; // TODO(matt): memory_book or other growable buffer + char *Base; + uint64_t Length; +} string; + +typedef struct +{ + char *Base; + char *Ptr; +} memory_page; + +typedef struct memory_pen_location +{ + memory_page *Page; + string String; + struct memory_pen_location *Prev; + struct memory_pen_location *Next; +} memory_pen_location; + +// TODO(matt): We probably want our memory_book to be able to support strings that span multiple pages +// SearchQuotes() especially could benefit from this + +typedef struct +{ + int64_t PageCount; + uint64_t PageSize; + uint64_t DataWidthInBytes; + uint64_t ItemCount; + memory_page *Pages; + memory_pen_location *Pen; +} memory_book; +#define _memory_book(type) memory_book + +typedef struct +{ + memory_book Message[2]; + memory_book *DesiredMessage; + memory_book *LastMessage; uint64_t RepetitionCount; bool LastMessageMayBeDeduplicated; } message_control; -message_control MESSAGE_CONTROL = { .RepetitionCount = 1 }; +#define SwapPtrs(A, B) void *Temp = A; A = B; B = Temp; + +message_control MESSAGE_CONTROL; #define Print(...) fprintf(__VA_ARGS__); MESSAGE_CONTROL.LastMessageMayBeDeduplicated = FALSE #define WriteToFile(...) fprintf(__VA_ARGS__) @@ -530,12 +568,6 @@ StringLength(char *String) return i; } -typedef struct -{ - char *Base; - uint64_t Length; -} string; - // NOTE(matt): For use with 0-terminated strings string Wrap0(char *String) @@ -562,328 +594,6 @@ Wrap0i_(char *S, uint64_t MaxSize) return Result; } -int -CopyBytes(char *Dest, char *Src, int Count) -{ - for(int i = 0; i < Count; ++i) - { - Dest[i] = Src[i]; - } - return Count; -} - -int -CopyStringToBarePtr(char *Dest, string Src) -{ - return CopyBytes(Dest, Src.Base, Src.Length); -} - -int -CopyStringCToBarePtr(colour_code Colour, char *Dest, string Src) -{ - int Length = 0; - Length += CopyStringToBarePtr(Dest + Length, Wrap0(ColourStrings[Colour])); - Length += CopyStringToBarePtr(Dest + Length, Src); - Length += CopyStringToBarePtr(Dest + Length, Wrap0(ColourStrings[CS_END])); - return Length; -} - -typedef struct -{ - char *Base; - char *Ptr; -} memory_page; - -typedef struct memory_pen_location -{ - memory_page *Page; - string String; - struct memory_pen_location *Prev; - struct memory_pen_location *Next; -} memory_pen_location; - -// TODO(matt): We probably want our memory_book to be able to support strings that span multiple pages -// SearchQuotes() especially could benefit from this - -typedef struct -{ - int64_t PageCount; - uint64_t PageSize; - uint64_t DataWidthInBytes; - uint64_t ItemCount; - memory_page *Pages; - memory_pen_location *Pen; -} memory_book; -#define _memory_book(type) memory_book - -memory_book -InitBook(uint64_t DataWidthInBytes, uint64_t ItemsPerPage) -{ - memory_book Result = {}; - Result.PageSize = ItemsPerPage * DataWidthInBytes; - Result.DataWidthInBytes = DataWidthInBytes; - return Result; -} - -memory_book -InitBookOfPointers(uint64_t ItemsPerPage) -{ - memory_book Result = {}; - Result.PageSize = ItemsPerPage * sizeof(uintptr_t); - Result.DataWidthInBytes = sizeof(uintptr_t); - return Result; -} - -memory_book -InitBookOfStrings(uint64_t PageSizeInBytes) -{ - memory_book Result = {}; - Result.PageSize = PageSizeInBytes; - Result.DataWidthInBytes = 1; - return Result; -} - -memory_page * -AddPage(memory_book *M) -{ - M->Pages = realloc(M->Pages, (M->PageCount + 1) * sizeof(memory_page)); - M->Pages[M->PageCount].Base = calloc(1, M->PageSize); - M->Pages[M->PageCount].Ptr = M->Pages[M->PageCount].Base; - ++M->PageCount; - return &M->Pages[M->PageCount - 1]; -} - -void -FreePage(memory_page *P) -{ - Free(P->Base); - P->Ptr = 0; -} - -void -FreeBook(memory_book *M) -{ - for(int i = 0; i < M->PageCount; ++i) - { - FreePage(&M->Pages[i]); - } - FreeAndResetCount(M->Pages, M->PageCount); - - memory_pen_location *This = M->Pen; - while(This) - { - M->Pen = M->Pen->Prev; - Free(This); - This = M->Pen; - } - - memory_book Zero = {}; - *M = Zero; -} - -void -FreeAndReinitialiseBook(memory_book *M) -{ - int PageSize = M->PageSize; - int DataWidthInBytes = M->DataWidthInBytes; - - FreeBook(M); - - M->PageSize = PageSize; - M->DataWidthInBytes = DataWidthInBytes; -} - -memory_page * -GetOrAddPageForString(memory_book *M, string S) -{ - for(int i = 0; i < M->PageCount; ++i) - { - if((M->Pages[i].Ptr - M->Pages[i].Base) + S.Length < M->PageSize) - { - return &M->Pages[i]; - } - } - return AddPage(M); -} - -memory_page * -GetOrAddPageForSize(memory_book *M, uint64_t Size) -{ - for(int i = 0; i < M->PageCount; ++i) - { - if((M->Pages[i].Ptr - M->Pages[i].Base) + Size < M->PageSize) - { - return &M->Pages[i]; - } - } - return AddPage(M); -} - -string -WriteStringInBook(memory_book *M, string S) -{ - string Result = {}; - memory_page *Page = GetOrAddPageForString(M, S); - - memory_pen_location *Pen = malloc(sizeof(memory_pen_location)); - Pen->Page = Page; - Pen->String.Base = Page->Ptr; - Pen->String.Length = S.Length; - - Pen->Prev = M->Pen; - Pen->Next = 0; - - if(M->Pen) - { - M->Pen->Next = Pen; - } - - M->Pen = Pen; - - Result.Base = Page->Ptr; - Result.Length = S.Length; - Page->Ptr += CopyStringToBarePtr(Page->Ptr, S); - return Result; -} - -typedef struct -{ - int64_t Page; - uint64_t Line; -} book_position; - -book_position -GetBookPositionFromIndex(memory_book *M, int Index) -{ - book_position Result = {}; - int ItemsPerPage = M->PageSize / M->DataWidthInBytes; - Result.Line = Index % ItemsPerPage; - Index -= Result.Line; - Result.Page = Index / ItemsPerPage; - return Result; -} - -void * -GetPlaceInBook(memory_book *M, int Index) -{ - void *Result = 0; - - book_position Position = GetBookPositionFromIndex(M, Index); - - memory_page *Page = M->Pages + Position.Page; - while((Position.Page + 1) > M->PageCount) - { - Page = AddPage(M); - } - - char *Ptr = Page->Base; - Ptr += M->DataWidthInBytes * Position.Line; - Result = Ptr; - - return Result; -} - -void * -MakeSpaceInBook(memory_book *M) -{ - return GetPlaceInBook(M, M->ItemCount++); -} - -string -ExtendStringInBook(memory_book *M, string S) -{ - string Result = {}; - if(M->Pen) - { - Result.Length = M->Pen->String.Length + S.Length; - if(M->Pen->String.Base - M->Pen->Page->Base + Result.Length < M->PageSize) - { - M->Pen->Page->Ptr += CopyStringToBarePtr(M->Pen->Page->Ptr, S); - } - else - { - memory_pen_location Pen = *M->Pen; - M->Pen->Page->Ptr = M->Pen->String.Base; - - memory_page *Page = GetOrAddPageForString(M, Result); - M->Pen->Page = Page; - M->Pen->String.Base = Page->Ptr; - Page->Ptr += CopyStringToBarePtr(Page->Ptr, Pen.String); - Page->Ptr += CopyStringToBarePtr(Page->Ptr, S); - } - Result.Base = M->Pen->String.Base; - M->Pen->String.Length = Result.Length; - } - else - { - Result = WriteStringInBook(M, S); - } - return Result; -} - -void -ResetPen(memory_book *M) -{ - if(M->Pen) - { - if(M->Pen->Page) - { - M->Pen->String.Base = M->Pen->Page->Ptr; - } - else - { - M->Pen->Page = AddPage(M); - M->Pen->String.Base = M->Pen->Page->Base; - } - M->Pen->String.Length = 0; - } -} - -void -EraseCurrentStringFromBook(memory_book *M) -{ - M->Pen->Page->Ptr -= M->Pen->String.Length; - - if(M->Pen->Prev) - { - M->Pen->Prev->Next = M->Pen->Next; - } - if(M->Pen->Next) - { - M->Pen->Next->Prev = M->Pen->Prev; - } - - memory_pen_location *This = M->Pen; - M->Pen = M->Pen->Prev; - Free(This); -} - -void -PrintPage(memory_page *P) -{ - Print(stderr, "%.*s\n\n", (int)(P->Ptr - P->Base), P->Base); -} - -void -PrintBookOfStrings(memory_book *M) -{ - for(int i = 0; i < M->PageCount; ++i) - { - PrintPage(&M->Pages[i]); - } -} - -int64_t -StringToInt(string S) -{ - int Result = 0; - for(int i = 0; i < S.Length; ++i) - { - Result = Result * 10 + S.Base[i] - '0'; - } - return Result; -} - int64_t StringsDiffer(string A, string B) { @@ -1088,6 +798,352 @@ StringsMatchCaseInsensitive(string A, string B) return FALSE; } +int +CopyBytes(char *Dest, char *Src, int Count) +{ + for(int i = 0; i < Count; ++i) + { + Dest[i] = Src[i]; + } + return Count; +} + +int +CopyStringToBarePtr(char *Dest, string Src) +{ + return CopyBytes(Dest, Src.Base, Src.Length); +} + +int +CopyStringCToBarePtr(colour_code Colour, char *Dest, string Src) +{ + int Length = 0; + Length += CopyStringToBarePtr(Dest + Length, Wrap0(ColourStrings[Colour])); + Length += CopyStringToBarePtr(Dest + Length, Src); + Length += CopyStringToBarePtr(Dest + Length, Wrap0(ColourStrings[CS_END])); + return Length; +} + +memory_book +InitBook(uint64_t DataWidthInBytes, uint64_t ItemsPerPage) +{ + memory_book Result = {}; + Result.PageSize = ItemsPerPage * DataWidthInBytes; + Result.DataWidthInBytes = DataWidthInBytes; + return Result; +} + +memory_book +InitBookOfPointers(uint64_t ItemsPerPage) +{ + memory_book Result = {}; + Result.PageSize = ItemsPerPage * sizeof(uintptr_t); + Result.DataWidthInBytes = sizeof(uintptr_t); + return Result; +} + +memory_book +InitBookOfStrings(uint64_t PageSizeInBytes) +{ + memory_book Result = {}; + Result.PageSize = PageSizeInBytes; + Result.DataWidthInBytes = 1; + return Result; +} + +memory_page * +AddPage(memory_book *M) +{ + M->Pages = realloc(M->Pages, (M->PageCount + 1) * sizeof(memory_page)); + M->Pages[M->PageCount].Base = calloc(1, M->PageSize); + M->Pages[M->PageCount].Ptr = M->Pages[M->PageCount].Base; + ++M->PageCount; + return &M->Pages[M->PageCount - 1]; +} + +void +FreePage(memory_page *P) +{ + Free(P->Base); + P->Ptr = 0; +} + +void +FreePens(memory_book *M) +{ + memory_pen_location *This = M->Pen; + while(This) + { + M->Pen = M->Pen->Prev; + Free(This); + This = M->Pen; + } +} + +void +FreeBook(memory_book *M) +{ + for(int i = 0; i < M->PageCount; ++i) + { + FreePage(&M->Pages[i]); + } + FreeAndResetCount(M->Pages, M->PageCount); + + FreePens(M); + + memory_book Zero = {}; + *M = Zero; +} + +void +FreeAndReinitialiseBook(memory_book *M) +{ + int PageSize = M->PageSize; + int DataWidthInBytes = M->DataWidthInBytes; + + FreeBook(M); + + M->PageSize = PageSize; + M->DataWidthInBytes = DataWidthInBytes; +} + +void +ResetBook(memory_book *M) +{ + for(int i = 0; i < M->PageCount; ++i) + { + memory_page *This = &M->Pages[i]; + This->Ptr = This->Base; + } + FreePens(M); +} + +memory_page * +GetOrAddPageForString(memory_book *M, string S) +{ + for(int i = 0; i < M->PageCount; ++i) + { + if((M->Pages[i].Ptr - M->Pages[i].Base) + S.Length < M->PageSize) + { + return &M->Pages[i]; + } + } + return AddPage(M); +} + +memory_page * +GetOrAddPageForSize(memory_book *M, uint64_t Size) +{ + for(int i = 0; i < M->PageCount; ++i) + { + if((M->Pages[i].Ptr - M->Pages[i].Base) + Size < M->PageSize) + { + return &M->Pages[i]; + } + } + return AddPage(M); +} + +string +WriteStringInBook(memory_book *M, string S) +{ + string Result = {}; + memory_page *Page = GetOrAddPageForString(M, S); + + memory_pen_location *Pen = malloc(sizeof(memory_pen_location)); + Pen->Page = Page; + Pen->String.Base = Page->Ptr; + Pen->String.Length = S.Length; + + Pen->Prev = M->Pen; + Pen->Next = 0; + + if(M->Pen) + { + M->Pen->Next = Pen; + } + + M->Pen = Pen; + + Result.Base = Page->Ptr; + Result.Length = S.Length; + Page->Ptr += CopyStringToBarePtr(Page->Ptr, S); + return Result; +} + +string +ExtendStringInBook(memory_book *M, string S) +{ + string Result = {}; + if(M->Pen) + { + Result.Length = M->Pen->String.Length + S.Length; + if(M->Pen->String.Base - M->Pen->Page->Base + Result.Length < M->PageSize) + { + M->Pen->Page->Ptr += CopyStringToBarePtr(M->Pen->Page->Ptr, S); + } + else + { + memory_pen_location Pen = *M->Pen; + M->Pen->Page->Ptr = M->Pen->String.Base; + + memory_page *Page = GetOrAddPageForString(M, Result); + M->Pen->Page = Page; + M->Pen->String.Base = Page->Ptr; + Page->Ptr += CopyStringToBarePtr(Page->Ptr, Pen.String); + Page->Ptr += CopyStringToBarePtr(Page->Ptr, S); + } + Result.Base = M->Pen->String.Base; + M->Pen->String.Length = Result.Length; + } + else + { + Result = WriteStringInBook(M, S); + } + return Result; +} + +void +ExtendStringCInBook(memory_book *M, colour_code Colour, string S) +{ + ExtendStringInBook(M, Wrap0(ColourStrings[Colour])); + ExtendStringInBook(M, S); + ExtendStringInBook(M, Wrap0(ColourStrings[CS_END])); +} + +typedef struct +{ + int64_t Page; + uint64_t Line; +} book_position; + +book_position +GetBookPositionFromIndex(memory_book *M, int Index) +{ + book_position Result = {}; + int ItemsPerPage = M->PageSize / M->DataWidthInBytes; + Result.Line = Index % ItemsPerPage; + Index -= Result.Line; + Result.Page = Index / ItemsPerPage; + return Result; +} + +void * +GetPlaceInBook(memory_book *M, int Index) +{ + void *Result = 0; + + book_position Position = GetBookPositionFromIndex(M, Index); + + memory_page *Page = M->Pages + Position.Page; + while((Position.Page + 1) > M->PageCount) + { + Page = AddPage(M); + } + + char *Ptr = Page->Base; + Ptr += M->DataWidthInBytes * Position.Line; + Result = Ptr; + + return Result; +} + +void * +MakeSpaceInBook(memory_book *M) +{ + return GetPlaceInBook(M, M->ItemCount++); +} + +void +ResetPen(memory_book *M) +{ + if(M->Pen) + { + if(M->Pen->Page) + { + M->Pen->String.Base = M->Pen->Page->Ptr; + } + else + { + M->Pen->Page = AddPage(M); + M->Pen->String.Base = M->Pen->Page->Base; + } + M->Pen->String.Length = 0; + } +} + +void +EraseCurrentStringFromBook(memory_book *M) +{ + M->Pen->Page->Ptr -= M->Pen->String.Length; + + if(M->Pen->Prev) + { + M->Pen->Prev->Next = M->Pen->Next; + } + if(M->Pen->Next) + { + M->Pen->Next->Prev = M->Pen->Prev; + } + + memory_pen_location *This = M->Pen; + M->Pen = M->Pen->Prev; + Free(This); +} + +bool +PagesMatch(memory_page *A, memory_page *B) +{ + return StringsMatch(Wrap0i_(A->Base, A->Ptr - A->Base), Wrap0i_(B->Base, B->Ptr - B->Base)); +} + +bool +BooksMatch(memory_book *A, memory_book *B) +{ + bool Result = A->PageCount == B->PageCount; + int i = 0; + for(; Result == TRUE && i < A->PageCount; ++i) + { + if(!PagesMatch(&A->Pages[i], &B->Pages[i])) + { + Result = FALSE; + break; + } + } + + if(Result == TRUE && i != A->PageCount) + { + Result = FALSE; + } + return Result; +} + +void +PrintPage(memory_page *P) +{ + Print(stderr, "%.*s", (int)(P->Ptr - P->Base), P->Base); +} + +void +PrintBookOfStrings(memory_book *M) +{ + for(int i = 0; i < M->PageCount; ++i) + { + PrintPage(&M->Pages[i]); + } +} + +int64_t +StringToInt(string S) +{ + int Result = 0; + for(int i = 0; i < S.Length; ++i) + { + Result = Result * 10 + S.Base[i] - '0'; + } + return Result; +} + string ExtensionStrings[] = { {}, @@ -2635,7 +2691,7 @@ GetIconTypeFromString(string *Filename, token *T) Print(stderr, " %s\n", IconTypeStrings[i]); } - return NS_COUNT; + return IT_COUNT; } char *VODPlatformStrings[] = @@ -4604,6 +4660,368 @@ CopyBuffer_(int LineNumber, buffer *Dest, buffer *Src) *Dest->Ptr = '\0'; } +// NOTE(matt): Clash Detection +// +char *ResolutionStatusStrings[] = +{ + "RS_NOT_STARTED", + " RS_SEEN", + " RS_PENDING", + " RS_RESOLVED", + " RS_REPORTED", +}; + +typedef enum +{ + RS_NOT_STARTED, // Earliest + RS_SEEN, + RS_PENDING, + RS_RESOLVED, + RS_REPORTED, // Latest +} resolution_status; + +typedef struct +{ + project *Project; + char CandidateBaseFilename[MAX_BASE_FILENAME_LENGTH]; + char CurrentOutputValue[MAX_ENTRY_OUTPUT_LENGTH]; + char DesiredOutputValue[MAX_ENTRY_OUTPUT_LENGTH]; + char IncumbentBaseFilename[MAX_BASE_FILENAME_LENGTH]; + resolution_status Status; + bool Vacating; + bool Superseding; +} clash_entry; + +char *ChainStructureStrings[] = +{ + "Closed Loop", + "Open Ended", +}; + +typedef enum +{ + CS_CLOSED_LOOP, + CS_OPEN_ENDED +} chain_structure; + +typedef struct +{ + memory_book Book[2]; + memory_book *Main; + memory_book *Holder; + memory_book Chain; + chain_structure ChainStructure; + bool ChainIsResolvable; + bool Resolving; +} clash_resolver; + +clash_resolver +InitClashResolver(void) +{ + clash_resolver Result = {}; + Result.Book[0] = InitBook(sizeof(clash_entry), 8); + Result.Book[1] = InitBook(sizeof(clash_entry), 8); + Result.Main = &Result.Book[0]; + Result.Holder = &Result.Book[1]; + Result.Chain = InitBookOfPointers(8); + Result.ChainStructure = CS_OPEN_ENDED; + Result.Resolving = FALSE; + return Result; +} + +void +ResetClashResolver(clash_resolver *R) +{ + FreeAndReinitialiseBook(&R->Book[0]); + FreeAndReinitialiseBook(&R->Book[1]); + R->Main = &R->Book[0]; + R->Holder = &R->Book[1]; + FreeAndReinitialiseBook(&R->Chain); + R->ChainStructure = CS_OPEN_ENDED; + R->Resolving = FALSE; +} + +clash_entry * +GetClash(memory_book *C, project *Project, string CandidateBaseFilename) +{ + clash_entry *Result = 0; + for(int i = 0; i < C->ItemCount; ++i) + { + clash_entry *This = GetPlaceInBook(C, i); + if(Project == This->Project && StringsMatch(CandidateBaseFilename, Wrap0i(This->CandidateBaseFilename))) + { + Result = This; + break; + } + } + return Result; +} + +clash_entry * +GetFellowCandidateOf(memory_book *C, clash_entry *Candidate) +{ + clash_entry *Result = 0; + string IncumbentBaseFilename = Wrap0i(Candidate->IncumbentBaseFilename); + for(int i = 0; i < C->ItemCount; ++i) + { + clash_entry *Fellow = GetPlaceInBook(C, i); + if(Candidate != Fellow + && Candidate->Project == Fellow->Project + && (Fellow->Status < RS_PENDING || Fellow->Status == RS_REPORTED) + && StringsMatch(IncumbentBaseFilename, Wrap0i(Fellow->IncumbentBaseFilename))) + { + Result = Fellow; + break; + } + } + return Result; +} + +clash_entry * +GetClashCandidateOf(memory_book *C, clash_entry *Incumbent) +{ + clash_entry *Result = 0; + string CurrentOutputValue = Wrap0i(Incumbent->CurrentOutputValue); + for(int i = 0; i < C->ItemCount; ++i) + { + clash_entry *Candidate = GetPlaceInBook(C, i); + if(Incumbent != Candidate + && Candidate->Project == Incumbent->Project + && !Candidate->Vacating + && (Candidate->Status < RS_PENDING || Candidate->Status == RS_REPORTED) + && StringsMatch(CurrentOutputValue, Wrap0i(Candidate->DesiredOutputValue))) + { + Result = Candidate; + break; + } + } + return Result; +} + +clash_entry * +GetClashIncumbentOf(memory_book *C, clash_entry *Candidate) +{ + clash_entry *Result = 0; + string IncumbentBaseFilename = Wrap0i(Candidate->IncumbentBaseFilename); + for(int i = 0; i < C->ItemCount; ++i) + { + clash_entry *Incumbent = GetPlaceInBook(C, i); + if(Candidate != Incumbent + && Candidate->Project == Incumbent->Project + && (Incumbent->Status < RS_PENDING || Incumbent->Status == RS_REPORTED) + && StringsMatch(IncumbentBaseFilename, Wrap0i(Incumbent->CandidateBaseFilename))) + { + Result = Incumbent; + break; + } + } + return Result; +} + +typedef enum +{ + CR_FELLOW, + CR_INCUMBENT, +} clash_relationship; + +typedef struct +{ + clash_relationship Relationship; + clash_entry *Entry; +} related_clash_entry; + +related_clash_entry +GetFellowOrClashIncumbentOf(memory_book *C, clash_entry *Candidate) +{ + related_clash_entry Result = {}; + Result.Entry = GetFellowCandidateOf(C, Candidate); + if(!Result.Entry) { Result.Entry = GetClashIncumbentOf(C, Candidate); Result.Relationship = CR_INCUMBENT; } + return Result; +} + +void +AbsolveIncumbentOf(memory_book *C, clash_entry *Candidate) +{ + clash_entry *Incumbent = GetClashIncumbentOf(C, Candidate); + if(Incumbent) + { + Incumbent->Status = RS_NOT_STARTED; + if(!GetFellowCandidateOf(C, Candidate) && Wrap0i(Incumbent->IncumbentBaseFilename).Length == 0) + { + Incumbent->Status = RS_RESOLVED; + } + Clear(Candidate->IncumbentBaseFilename, sizeof(Candidate->IncumbentBaseFilename)); + } +} + +void +VacatePotentialClash(memory_book *C, project *Project, string OutgoingIncumbentBaseFilename) +{ + clash_entry *OutgoingIncumbent = GetClash(C, Project, OutgoingIncumbentBaseFilename); + if(OutgoingIncumbent) + { + AbsolveIncumbentOf(C, OutgoingIncumbent); + OutgoingIncumbent->Vacating = TRUE; + } +} + +void +ResolveClash(memory_book *C, project *Project, string BaseFilename) +{ + clash_entry *This = GetClash(C, Project, BaseFilename); + if(This) + { + AbsolveIncumbentOf(C, This); + if(!GetClashCandidateOf(C, This)) + { + This->Status = RS_RESOLVED; + } + else + { + This->Status = RS_NOT_STARTED; + } + } +} + +void +PushClash(memory_book *C, project *Project, db_entry *IncumbentEntry, string CandidateBaseFilename, string CurrentOutputValue, string DesiredOutputValue) +{ + clash_entry *This = GetClash(C, Project, CandidateBaseFilename); + if(!This) + { + This = MakeSpaceInBook(C); + } + else if(StringsDiffer(Wrap0i(This->DesiredOutputValue), DesiredOutputValue)) + { + AbsolveIncumbentOf(C, This); + } + This->Project = Project; + ClearCopyStringNoFormatOrTerminate(This->CandidateBaseFilename, sizeof(This->CandidateBaseFilename), CandidateBaseFilename); + ClearCopyStringNoFormatOrTerminate(This->CurrentOutputValue, sizeof(This->CurrentOutputValue), CurrentOutputValue); + ClearCopyStringNoFormatOrTerminate(This->DesiredOutputValue, sizeof(This->DesiredOutputValue), DesiredOutputValue); + ClearCopyStringNoFormatOrTerminate(This->IncumbentBaseFilename, sizeof(This->IncumbentBaseFilename), Wrap0i(IncumbentEntry->HMMLBaseFilename)); + This->Status = RS_NOT_STARTED; + + if(IncumbentEntry) + { + string IncumbentBaseFilename = Wrap0i(IncumbentEntry->HMMLBaseFilename); + clash_entry *Incumbent = GetClash(C, Project, IncumbentBaseFilename); + if(!Incumbent) + { + Incumbent = MakeSpaceInBook(C); + Incumbent->Project = Project; + ClearCopyStringNoFormatOrTerminate(Incumbent->CandidateBaseFilename, sizeof(Incumbent->CandidateBaseFilename), IncumbentBaseFilename); + string IncumbentOutput = Wrap0i(IncumbentEntry->OutputLocation); + ClearCopyStringNoFormatOrTerminate(Incumbent->CurrentOutputValue, sizeof(Incumbent->CurrentOutputValue), IncumbentOutput); + ClearCopyStringNoFormatOrTerminate(Incumbent->DesiredOutputValue, sizeof(Incumbent->DesiredOutputValue), IncumbentOutput); + } + else if(Incumbent->Status != RS_REPORTED) + { + Incumbent->Status = RS_NOT_STARTED; + } + } +} + +clash_entry ** +PushClashPtr(memory_book *C, clash_entry *E) +{ + clash_entry **New = MakeSpaceInBook(C); + *New = E; + return New; +} + +#if DEBUG_CLASH_RESOLVER +void +PrintClash(memory_book *C, clash_entry *E) +{ + PrintC(CS_CYAN, ResolutionStatusStrings[E->Status]); + PrintC(E->Vacating ? CS_GREEN : CS_RED, " V"); + PrintC(E->Superseding ? CS_GREEN : CS_RED, " S "); + + PrintLineage(E->Project->Lineage, FALSE); + Print(stderr, "/"); + + PrintStringC(CS_MAGENTA, Wrap0i(E->CandidateBaseFilename)); + Print(stderr, ": "); + + string CurrentOutputValue = Wrap0i(E->CurrentOutputValue); + string DesiredOutputValue = Wrap0i(E->DesiredOutputValue); + if(CurrentOutputValue.Length > 0) + { + PrintStringC(E->Vacating ? CS_BLACK_BOLD : CS_WHITE, CurrentOutputValue); + } + else + { + PrintStringC(CS_YELLOW, Wrap0("(null)")); + } + + if( +#if 0 + !E->Vacating && StringsDiffer(CurrentOutputValue, DesiredOutputValue) +#else + 1 +#endif + ) + { + Print(stderr, " → "); + string IncumbentBaseFilename = Wrap0i(E->IncumbentBaseFilename); + clash_entry *Incumbent = GetClash(C, E->Project, IncumbentBaseFilename); + if(Incumbent +#if 0 + && !Incumbent->Vacating +#else + || IncumbentBaseFilename.Length > 0 +#endif + ) + { + PrintStringC(CS_RED_BOLD, DesiredOutputValue); + Print(stderr, " ("); + PrintStringC(CS_MAGENTA, IncumbentBaseFilename); + Print(stderr, ")"); + } + else + { + PrintStringC(CS_GREEN_BOLD, DesiredOutputValue); + } + } +} + +void +PrintClashes(memory_book *C, int LineNumber) +{ + if(C->ItemCount) + { + Print(stderr, "\n"); + PrintLinedFunctionName(LineNumber, "PrintClashes()"); + } + for(int i = 0; i < C->ItemCount; ++i) + { + clash_entry *This = GetPlaceInBook(C, i); + PrintClash(C, This); + Print(stderr, "\n"); + } +} + +void +PrintClashChain(clash_resolver *R) +{ + if(R->Chain.ItemCount) + { + } + for(int i = 0; i < R->Chain.ItemCount; ++i) + { + clash_entry **This = GetPlaceInBook(&R->Chain, i); + Colourise(CS_BLACK_BOLD); + Print(stderr, " [%i] ", i); + Colourise(CS_END); + PrintClash(R->Main, *This); + Print(stderr, "\n"); + } +} +#endif +// +// NOTE(matt): Clash Detection + typedef enum { PAGE_PLAYER = 1 << 0, @@ -10002,9 +10420,9 @@ DeclaimPlayerBuffers(player_buffers *B) } char * -ConstructIndexFilePath(string BaseDir, string SearchLocation, string ProjectID) +ConstructIndexFilePath(string *BaseDir, string *SearchLocation, string ProjectID) { - char *Result = ConstructDirectoryPath(&BaseDir, &SearchLocation, 0); + char *Result = ConstructDirectoryPath(BaseDir, SearchLocation, 0); ExtendString0(&Result, Wrap0("/")); ExtendString0(&Result, ProjectID); ExtendString0(&Result, ExtensionStrings[EXT_INDEX]); @@ -10018,9 +10436,9 @@ DeleteSearchPageFromFilesystem(string BaseDir, string SearchLocation, string Pro remove(SearchPagePath); Free(SearchPagePath); - remove(DB.File.Path); - // TODO(matt): Consider the correctness of this. - FreeFile(&DB.File, NA); + char *IndexFilePath = ConstructIndexFilePath(&BaseDir, &SearchLocation, ProjectID); + remove(IndexFilePath); + Free(IndexFilePath); char *SearchDirectory = ConstructDirectoryPath(&BaseDir, &SearchLocation, 0); remove(SearchDirectory); @@ -10047,15 +10465,13 @@ PrintLineageAndEntryID(string Lineage, string EntryID, bool AppendNewline) if(AppendNewline) { Print(stderr, "\n"); } } -int -CopyLineageAndEntryIDToBarePtr(char *Ptr, string Lineage, string EntryID, bool AppendNewline) +void +ExtendLineageAndEntryIDInBook(memory_book *M, string Lineage, string EntryID, bool AppendNewline) { - int Length = 0; - Length += CopyLineageToBarePtr(Ptr + Length, Lineage, FALSE); - Length += CopyStringToBarePtr(Ptr + Length, Wrap0("/")); - Length += CopyStringCToBarePtr(CS_MAGENTA, Ptr + Length, EntryID); - if(AppendNewline) { Length += CopyStringToBarePtr(Ptr + Length, Wrap0("\n")); } - return Length; + ExtendLineageInBook(M, Lineage, FALSE); + ExtendStringInBook(M, Wrap0("/")); + ExtendStringCInBook(M, CS_MAGENTA, EntryID); + if(AppendNewline) { ExtendStringInBook(M, Wrap0("\n")); } } void @@ -10069,17 +10485,15 @@ PrintLineageAndEntry(string Lineage, string EntryID, string EntryTitle, bool App if(AppendNewline) { Print(stderr, "\n"); } } -int -CopyLineageAndEntryToBarePtr(char *Ptr, string Lineage, string EntryID, string EntryTitle, bool AppendNewline) +void +ExtendLineageAndEntryInBook(memory_book *M, string Lineage, string EntryID, string EntryTitle, bool AppendNewline) { - int Length = 0; - Length += CopyLineageAndEntryIDToBarePtr(Ptr + Length, Lineage, EntryID, FALSE); - Length += CopyStringToBarePtr(Ptr + Length, Wrap0(ColourStrings[CS_MAGENTA])); - Length += CopyStringToBarePtr(Ptr + Length, Wrap0(" - ")); - Length += CopyStringToBarePtr(Ptr + Length, EntryTitle); - Length += CopyStringToBarePtr(Ptr + Length, Wrap0(ColourStrings[CS_END])); - if(AppendNewline) { Length += CopyStringToBarePtr(Ptr + Length, Wrap0("\n")); } - return Length; + ExtendLineageAndEntryIDInBook(M, Lineage, EntryID, FALSE); + ExtendStringInBook(M, Wrap0(ColourStrings[CS_MAGENTA])); + ExtendStringInBook(M, Wrap0(" - ")); + ExtendStringInBook(M, EntryTitle); + ExtendStringInBook(M, Wrap0(ColourStrings[CS_END])); + if(AppendNewline) { ExtendStringInBook(M, Wrap0("\n")); } } void @@ -10093,18 +10507,23 @@ ClearLastMessage(void) void PrintEdit(edit_type_id EditType, string Lineage, string EntryID, string *EntryTitle, bool Private) { - char *Ptr = MESSAGE_CONTROL.DesiredMessage; - Ptr += CopyString(Ptr, sizeof(MESSAGE_CONTROL.DesiredMessage), "%s%s%s%s ", ColourStrings[EditTypes[EditType].Colour], Private ? "Privately " : "", EditTypes[EditType].Name, ColourStrings[CS_END]); + memory_book *Desired = MESSAGE_CONTROL.DesiredMessage; + ResetPen(Desired); + ExtendStringInBook(Desired, Wrap0(ColourStrings[EditTypes[EditType].Colour])); + ExtendStringInBook(Desired, Wrap0(Private ? "Privately " : "")); + ExtendStringInBook(Desired, Wrap0(EditTypes[EditType].Name)); + ExtendStringInBook(Desired, Wrap0(ColourStrings[CS_END])); + ExtendStringInBook(Desired, Wrap0(" ")); if(EntryTitle) { - Ptr += CopyLineageAndEntryToBarePtr(Ptr, CurrentProject->Lineage, EntryID, *EntryTitle, TRUE); + ExtendLineageAndEntryInBook(Desired, CurrentProject->Lineage, EntryID, *EntryTitle, TRUE); } else { - Ptr += CopyLineageAndEntryIDToBarePtr(Ptr, CurrentProject->Lineage, EntryID, TRUE); + ExtendLineageAndEntryIDInBook(Desired, CurrentProject->Lineage, EntryID, TRUE); } - if(MESSAGE_CONTROL.LastMessageMayBeDeduplicated && StringsMatch(Wrap0i(MESSAGE_CONTROL.LastMessage), Wrap0i(MESSAGE_CONTROL.DesiredMessage))) + if(MESSAGE_CONTROL.LastMessageMayBeDeduplicated && BooksMatch(MESSAGE_CONTROL.LastMessage, MESSAGE_CONTROL.DesiredMessage)) { ClearLastMessage(); ++MESSAGE_CONTROL.RepetitionCount; @@ -10115,9 +10534,9 @@ PrintEdit(edit_type_id EditType, string Lineage, string EntryID, string *EntryTi MESSAGE_CONTROL.RepetitionCount = 1; } - PrintString(Wrap0i(MESSAGE_CONTROL.DesiredMessage)); - ClearCopyStringNoFormatOrTerminate(MESSAGE_CONTROL.LastMessage, sizeof(MESSAGE_CONTROL.LastMessage), Wrap0i(MESSAGE_CONTROL.DesiredMessage)); - Clear(MESSAGE_CONTROL.DesiredMessage, sizeof(MESSAGE_CONTROL.DesiredMessage)); + PrintBookOfStrings(MESSAGE_CONTROL.DesiredMessage); + ResetBook(MESSAGE_CONTROL.LastMessage); + SwapPtrs(MESSAGE_CONTROL.DesiredMessage, MESSAGE_CONTROL.LastMessage); MESSAGE_CONTROL.LastMessageMayBeDeduplicated = TRUE; } @@ -10951,7 +11370,7 @@ FieldClashes_String(db_header_project *Header, uint32_t FieldOffset, uint32_t Fi } rc -HMMLToBuffers(buffers *CollationBuffers, template *BespokeTemplate, string BaseFilename, neighbourhood *N, uint64_t EntryCount) +HMMLToBuffers(buffers *CollationBuffers, template *BespokeTemplate, string BaseFilename, neighbourhood *N, clash_resolver *ClashResolver, uint64_t EntryCount) { MEM_TEST_TOP(); rc Result = RC_SUCCESS; @@ -11168,18 +11587,31 @@ HMMLToBuffers(buffers *CollationBuffers, template *BespokeTemplate, string BaseF if(Result == RC_SUCCESS) // Clash Tests { - db_entry *ClashTest = FieldClashes_String(N->Project, offsetof(db_entry, OutputLocation), sizeof(N->WorkingThis.OutputLocation), BaseFilename, OutputLocation); - if(ClashTest) + if(!ClashResolver->Resolving) { - // NOTE(matt): This clash is fatal because letting it go through would make the encroaching entry's - // HTML file overwrite that of an existing entry - IndexingErrorClash(&FilepathL, 0, S_ERROR, "output", OutputLocation, Wrap0i(ClashTest->HMMLBaseFilename)); - Result = RC_ERROR_HMML; + db_entry *ClashTest = FieldClashes_String(N->Project, offsetof(db_entry, OutputLocation), sizeof(N->WorkingThis.OutputLocation), BaseFilename, OutputLocation); + if(ClashTest) + { + // NOTE(matt): This clash is fatal because letting it go through would make the encroaching entry's + // HTML file overwrite that of an existing entry + + PushClash(ClashResolver->Main, CurrentProject, ClashTest, BaseFilename, Wrap0i(N->WorkingThis.OutputLocation), OutputLocation); + //IndexingErrorClash(&FilepathL, 0, S_ERROR, "output", OutputLocation, Wrap0i(ClashTest->HMMLBaseFilename)); + Result = RC_ERROR_HMML; + } + else + { + clash_entry *IncumbentSelf = GetClash(ClashResolver->Main, CurrentProject, BaseFilename); + if(IncumbentSelf) + { + ClearCopyStringNoFormatOrTerminate(IncumbentSelf->DesiredOutputValue, sizeof(IncumbentSelf->DesiredOutputValue), OutputLocation); + } + } } if(CurrentProject->Numbering.Method == NM_HMML_SPECIFIED) { - ClashTest = FieldClashes_String(N->Project, offsetof(db_entry, Number), sizeof(N->WorkingThis.Number), BaseFilename, Wrap0i(N->WorkingThis.Number)); + db_entry *ClashTest = FieldClashes_String(N->Project, offsetof(db_entry, Number), sizeof(N->WorkingThis.Number), BaseFilename, Wrap0i(N->WorkingThis.Number)); if(ClashTest) { // NOTE(matt): This clash is nonfatal because the number has no purpose other than to be @@ -11248,14 +11680,20 @@ HMMLToBuffers(buffers *CollationBuffers, template *BespokeTemplate, string BaseF CopyString(CollationBuffers->URLPlayer, sizeof(CollationBuffers->URLPlayer), "%s", URLPlayer.Location); DeclaimBuffer(&URLPlayer); - if(N->This) + if(!ClashResolver->Resolving || ClashResolver->ChainStructure == CS_OPEN_ENDED) { - string OldOutputLocation = Wrap0i(N->This->OutputLocation); - string NewOutputLocation = Wrap0i(N->WorkingThis.OutputLocation); - if(StringsDiffer(OldOutputLocation, NewOutputLocation)) + if(N->This) { - DeletePlayerPageFromFilesystem(CurrentProject->BaseDir, CurrentProject->PlayerLocation, OldOutputLocation, FALSE, TRUE); + string OldOutputLocation = Wrap0i(N->This->OutputLocation); + string NewOutputLocation = Wrap0i(N->WorkingThis.OutputLocation); + if(StringsDiffer(OldOutputLocation, NewOutputLocation)) + { + DeletePlayerPageFromFilesystem(CurrentProject->BaseDir, CurrentProject->PlayerLocation, OldOutputLocation, FALSE, TRUE); + VacatePotentialClash(ClashResolver->Main, CurrentProject, BaseFilename); + } } + + ResolveClash(ClashResolver->Main, CurrentProject, BaseFilename); } // TODO(matt): Handle art and art_variants, once .hmml supports them @@ -12623,7 +13061,7 @@ void InitIndexFile(project *P) { DB.File.Buffer.ID = BID_DATABASE; - DB.File.Path = ConstructIndexFilePath(P->BaseDir, P->SearchLocation, P->ID); + DB.File.Path = ConstructIndexFilePath(&P->BaseDir, &P->SearchLocation, P->ID); ReadFileIntoBuffer(&DB.File); // NOTE(matt): Could we actually catch errors (permissions?) here and bail? if(!DB.File.Buffer.Location) { @@ -13026,7 +13464,7 @@ RenumberEntries(neighbourhood *N, db_header_project *P, int64_t IndexOfFirstEntr } db_entry * -InsertIntoDB(neighbourhood *N, buffers *CollationBuffers, template *BespokeTemplate, string BaseFilename, bool RecheckingPrivacy, bool *Reinserting) +InsertIntoDB(neighbourhood *N, clash_resolver *ClashResolver, buffers *CollationBuffers, template *BespokeTemplate, string BaseFilename, bool RecheckingPrivacy, bool *Reinserting) { MEM_TEST_TOP(); db_entry *Result = 0; @@ -13072,7 +13510,7 @@ InsertIntoDB(neighbourhood *N, buffers *CollationBuffers, template *BespokeTempl bool VideoIsPrivate = FALSE; /* */ MEM_TEST_MID(); - /* +MEM */ rc HMMLToBuffersReturn = HMMLToBuffers(CollationBuffers, BespokeTemplate, BaseFilename, N, N->Project->EntryCount + (EditType != EDIT_REINSERTION)); + /* +MEM */ rc HMMLToBuffersReturn = HMMLToBuffers(CollationBuffers, BespokeTemplate, BaseFilename, N, ClashResolver, N->Project->EntryCount + (EditType != EDIT_REINSERTION)); /* */ MEM_TEST_MID(); if(HMMLToBuffersReturn == RC_SUCCESS || HMMLToBuffersReturn == RC_PRIVATE_VIDEO) { @@ -15277,7 +15715,7 @@ GenerateSearchPages(neighbourhood *N, buffers *CollationBuffers) } rc -DeleteEntry(neighbourhood *N, string BaseFilename) +DeleteEntry(neighbourhood *N, clash_resolver *ClashResolver, string BaseFilename) { rc Result = RC_NOOP; @@ -15290,21 +15728,26 @@ DeleteEntry(neighbourhood *N, string BaseFilename) string BaseDir = Wrap0i(N->Project->BaseDir); string PlayerLocation = Wrap0i(N->Project->PlayerLocation); DeletePlayerPageFromFilesystem(BaseDir, PlayerLocation, Wrap0i(Deceased.OutputLocation), FALSE, TRUE); + VacatePotentialClash(ClashResolver->Main, CurrentProject, BaseFilename); UpdateLandmarksForNeighbourhood(N, EDIT_DELETION); Result = RC_SUCCESS; } + else + { + ResolveClash(ClashResolver->Main, CurrentProject, BaseFilename); + } return Result; } rc -InsertEntry(neighbourhood *N, buffers *CollationBuffers, template *BespokeTemplate, string BaseFilename, bool RecheckingPrivacy) +InsertEntry(neighbourhood *N, clash_resolver *ClashResolver, buffers *CollationBuffers, template *BespokeTemplate, string BaseFilename, bool RecheckingPrivacy) { rc Result = RC_SUCCESS; MEM_TEST_TOP(); bool Reinserting = FALSE; /* */ MEM_TEST_MID(); - /* +MEM */ db_entry *Entry = InsertIntoDB(N, CollationBuffers, BespokeTemplate, BaseFilename, RecheckingPrivacy, &Reinserting); + /* +MEM */ db_entry *Entry = InsertIntoDB(N, ClashResolver, CollationBuffers, BespokeTemplate, BaseFilename, RecheckingPrivacy, &Reinserting); /* */ MEM_TEST_MID(); if(Entry) { @@ -15350,8 +15793,280 @@ SetCurrentProject(project *P, neighbourhood *N) } } +// NOTE(matt): Clash Resolution +// +clash_entry * +FindFirstClashNotStarted(memory_book *C) +{ + clash_entry *Result = 0; + for(int i = 0; i < C->ItemCount; ++i) + { + clash_entry *This = GetPlaceInBook(C, i); + if(This->Status == RS_NOT_STARTED || This->Vacating) + { + Result = This; + break; + } + } + return Result; +} + +clash_entry * +FindFirstInChain(memory_book *C, clash_entry *Clash) +{ + clash_entry *Result = Clash; + while(Result + && (Result->Status == RS_NOT_STARTED || Result->Status == RS_REPORTED) + && Wrap0i(Result->CurrentOutputValue).Length > 0) + { + Result->Status = RS_SEEN; + // TODO(matt): GetBestClashCandidateOf() + clash_entry *Candidate = GetClashCandidateOf(C, Result); + if(Candidate) + { + Result = Candidate; + } + } + return Result; +} + void -RecheckPrivacyRecursively(project *P, neighbourhood *N, buffers *CollationBuffers, template *BespokeTemplate) +BuildChain(clash_resolver *R, clash_entry *First) +{ + related_clash_entry This = { .Entry = First, .Relationship = CR_INCUMBENT }; + clash_entry **Final = PushClashPtr(&R->Chain, This.Entry); + This.Entry->Status = RS_PENDING; + + while(This.Entry + && !(*Final)->Vacating + && First->Project == (*Final)->Project + && Wrap0i((*Final)->IncumbentBaseFilename).Length > 0) + { + This = GetFellowOrClashIncumbentOf(R->Main, *Final); + if(This.Entry) + { + if(This.Relationship == CR_INCUMBENT) + { + Final = PushClashPtr(&R->Chain, This.Entry); + } + This.Entry->Status = RS_PENDING; + } + } +} + +bool +SetResolvability(clash_resolver *R) +{ + R->ChainIsResolvable = FALSE; + R->ChainStructure = CS_OPEN_ENDED; + if(R->Chain.ItemCount > 1) + { + clash_entry **First = GetPlaceInBook(&R->Chain, 0); + clash_entry **Final = GetPlaceInBook(&R->Chain, R->Chain.ItemCount - 1); + if(Wrap0i((*Final)->IncumbentBaseFilename).Length == 0) + { + R->ChainStructure = CS_OPEN_ENDED; + if((*Final)->Vacating || StringsDiffer(Wrap0i((*Final)->DesiredOutputValue), Wrap0i((*Final)->CurrentOutputValue))) + { + R->ChainIsResolvable = TRUE; + } + } + else if(StringsMatch(Wrap0i((*Final)->DesiredOutputValue), Wrap0i((*First)->CurrentOutputValue))) + { + R->ChainStructure = CS_CLOSED_LOOP; + R->ChainIsResolvable = TRUE; + } + } + return R->ChainIsResolvable; +} + +void +AffirmSuccessors(clash_resolver *R) +{ + for(int i = 0; i < R->Chain.ItemCount; ++i) + { + clash_entry **Successor = GetPlaceInBook(&R->Chain, i); + string IncumbentBaseFilename = Wrap0i((*Successor)->IncumbentBaseFilename); + string SuccessorBaseFilename = Wrap0i((*Successor)->CandidateBaseFilename); + + for(int j = 0; j < R->Main->ItemCount; ++j) + { + clash_entry *Fellow = GetPlaceInBook(R->Main, j); + if(*Successor != Fellow + && (*Successor)->Project == Fellow->Project + && Fellow->Status == RS_PENDING + && StringsMatch(IncumbentBaseFilename, Wrap0i(Fellow->IncumbentBaseFilename))) + { + (*Successor)->Superseding = TRUE; + ClearCopyStringNoFormatOrTerminate(Fellow->IncumbentBaseFilename, sizeof(Fellow->IncumbentBaseFilename), SuccessorBaseFilename); + } + } + } +} + +void +ReportPendingClashes(clash_resolver *R) +{ + for(int i = 0; i < R->Main->ItemCount; ++i) + { + clash_entry *This = GetPlaceInBook(R->Main, i); + if(This->Status == RS_PENDING) + { + string IncumbentBaseFilename = Wrap0i(This->IncumbentBaseFilename); + if(IncumbentBaseFilename.Length > 0) + { + string CandidateBaseFilename = Wrap0i(This->CandidateBaseFilename); + int NullTerminationBytes = 1; + char Filepath[This->Project->HMMLDir.Length + sizeof("/")-1 + CandidateBaseFilename.Length + ExtensionStrings[EXT_HMML].Length + NullTerminationBytes]; + char *P = Filepath; + P += CopyStringToBarePtr(P, This->Project->HMMLDir); + P += CopyStringToBarePtr(P, Wrap0("/")); + P += CopyStringToBarePtr(P, CandidateBaseFilename); + P += CopyStringToBarePtr(P, ExtensionStrings[EXT_HMML]); + *P = '\0'; + + string FilepathL = Wrap0(Filepath); + IndexingErrorClash(&FilepathL, 0, S_ERROR, "output", Wrap0i(This->DesiredOutputValue), IncumbentBaseFilename); + + clash_entry *Incumbent = GetClash(R->Main, This->Project, IncumbentBaseFilename); + if(Incumbent && Incumbent->Status == RS_RESOLVED) + { + Incumbent->Status = RS_REPORTED; + } + } + This->Status = RS_REPORTED; + } + } +} + +bool +ResolveChain(clash_resolver *R, + neighbourhood *N, buffers *CollationBuffers, template *BespokeTemplate, bool RecheckingPrivacy) +{ + bool Result = FALSE; + if(R->ChainIsResolvable) + { + AffirmSuccessors(R); + + clash_entry **First = GetPlaceInBook(&R->Chain, 0); + SetCurrentProject((*First)->Project, N); + // NOTE(matt): Resolving the chain in reverse (incumbents first) so that each extant Player Page will be deleted while + // generating the new one, before the following candidate generates their own Player Page there, rather than vice versa + for(int i = R->Chain.ItemCount - 1; i >= 0; --i) + { + clash_entry **This = GetPlaceInBook(&R->Chain, i); + if((*This)->Vacating || InsertEntry(N, R, CollationBuffers, BespokeTemplate, Wrap0i((*This)->CandidateBaseFilename), RecheckingPrivacy) == RC_SUCCESS) + { + (*This)->Status = RS_RESOLVED; + (*This)->Vacating = FALSE; + Result = TRUE; + } + if((*This)->Superseding) + { + Clear((*This)->IncumbentBaseFilename, sizeof((*This)->IncumbentBaseFilename)); + ClearCopyStringNoFormatOrTerminate((*This)->CurrentOutputValue, sizeof((*This)->CurrentOutputValue), Wrap0i((*This)->DesiredOutputValue)); + (*This)->Status = RS_REPORTED; + (*This)->Superseding = FALSE; + } + } + } + ReportPendingClashes(R); + return Result; +} + +clash_entry * +CopyClash(memory_book *Dest, clash_entry *Entry) +{ + clash_entry *Result = MakeSpaceInBook(Dest); + *Result = *Entry; + Result->Status = RS_REPORTED; + return Result; +} + +void +PurgeResolvedClashes(clash_resolver *R) +{ + for(int i = 0; i < R->Main->ItemCount; ++i) + { + clash_entry *This = GetPlaceInBook(R->Main, i); + if(This->Status != RS_RESOLVED) + { + CopyClash(R->Holder, This); + } + } + FreeAndReinitialiseBook(R->Main); + SwapPtrs(R->Main, R->Holder); + R->ChainStructure = CS_OPEN_ENDED; + R->Resolving = FALSE; +} + +void +ConfirmVacationStatus(clash_resolver *R) +{ + for(int i = 0; i < R->Main->ItemCount; ++i) + { + clash_entry *This = GetPlaceInBook(R->Main, i); + if(This->Vacating && !GetClashCandidateOf(R->Main, This)) + { + This->Vacating = FALSE; + } + } +} + +#define ResolveClashes(R, N, CollationBuffers, BespokeTemplates, RecheckingPrivacy) ResolveClashes_(R, __LINE__, N, CollationBuffers, BespokeTemplates, RecheckingPrivacy) +rc +ResolveClashes_(clash_resolver *R, int LineNumber, + neighbourhood *N, buffers *CollationBuffers, template *BespokeTemplate, bool RecheckingPrivacy) +{ + rc Result = RC_NOOP; + +#if DEBUG_CLASH_RESOLVER + PrintClashes(R->Main, LineNumber); +#endif + + R->Resolving = TRUE; + + ConfirmVacationStatus(R); + + clash_entry *Cursor = FindFirstClashNotStarted(R->Main); + while(Cursor) + { + Cursor = FindFirstInChain(R->Main, Cursor); + BuildChain(R, Cursor); + +#if DEBUG_CLASH_RESOLVER + Print(stderr, " Clash chain:\n"); + PrintClashChain(R); + if(SetResolvability(R)) + { + Colourise(CS_GREEN); + Print(stderr, " ↑ Chain (%s) is resolvable!\n", ChainStructureStrings[R->ChainStructure]); + } + else + { + Colourise(CS_RED); + Print(stderr, " ↑ Chain (%s) is not resolvable :(\n", ChainStructureStrings[R->ChainStructure]); + } + Colourise(CS_END); +#else + SetResolvability(R); +#endif + + if(ResolveChain(R, N, CollationBuffers, BespokeTemplate, RecheckingPrivacy)) + { + Result = RC_SUCCESS; + } + FreeAndReinitialiseBook(&R->Chain); + Cursor = FindFirstClashNotStarted(R->Main); + } + PurgeResolvedClashes(R); + return Result; +} +// +// NOTE(matt): Clash Resolution + +void +RecheckPrivacyRecursively(project *P, neighbourhood *N, clash_resolver *ClashResolver, buffers *CollationBuffers, template *BespokeTemplate) { SetCurrentProject(P, N); if(DB.Metadata.File.Buffer.Size > 0) @@ -15375,9 +16090,11 @@ RecheckPrivacyRecursively(project *P, neighbourhood *N, buffers *CollationBuffer for(int i = 0; i < PrivateEntryIndex; ++i) { - Inserted = (InsertEntry(N, CollationBuffers, BespokeTemplate, Wrap0i(PrivateEntries[i].HMMLBaseFilename), TRUE) == RC_SUCCESS); + Inserted |= (InsertEntry(N, ClashResolver, CollationBuffers, BespokeTemplate, Wrap0i(PrivateEntries[i].HMMLBaseFilename), TRUE) == RC_SUCCESS); } + Inserted |= (ResolveClashes(ClashResolver, N, CollationBuffers, BespokeTemplate, TRUE) == RC_SUCCESS); + if(Inserted) { GenerateSearchPages(N, CollationBuffers); @@ -15388,16 +16105,16 @@ RecheckPrivacyRecursively(project *P, neighbourhood *N, buffers *CollationBuffer for(int i = 0; i < P->Child.ItemCount; ++i) { - RecheckPrivacyRecursively(GetPlaceInBook(&P->Child, i), N, CollationBuffers, BespokeTemplate); + RecheckPrivacyRecursively(GetPlaceInBook(&P->Child, i), N, ClashResolver, CollationBuffers, BespokeTemplate); } } void -RecheckPrivacy(neighbourhood *N, buffers *CollationBuffers, template *BespokeTemplate) +RecheckPrivacy(neighbourhood *N, clash_resolver *ClashResolver, buffers *CollationBuffers, template *BespokeTemplate) { for(int i = 0; i < Config->Project.ItemCount; ++i) { - RecheckPrivacyRecursively(GetPlaceInBook(&Config->Project, i), N, CollationBuffers, BespokeTemplate); + RecheckPrivacyRecursively(GetPlaceInBook(&Config->Project, i), N, ClashResolver, CollationBuffers, BespokeTemplate); } LastPrivacyCheck = time(0); } @@ -15527,7 +16244,7 @@ typedef struct } entry_presence_id; // Metadata, unless we actually want to bolster this? rc -DeleteDeadDBEntries(neighbourhood *N, bool *Modified) +DeleteDeadDBEntries(neighbourhood *N, clash_resolver *ClashResolver, bool *Modified) { // TODO(matt): Additionally remove the BaseDir if it changed bool HasNewPlayerLocation = FALSE; @@ -15596,8 +16313,8 @@ DeleteDeadDBEntries(neighbourhood *N, bool *Modified) Free(OldSearchPagePath); Free(NewSearchPagePath); - char *OldSearchIndexPath = ConstructIndexFilePath(OldBaseDir, OldSearchLocation, Wrap0i(N->Project->ID)); - char *NewSearchIndexPath = ConstructIndexFilePath(NewBaseDir, NewSearchLocation, Wrap0i(N->Project->ID)); + char *OldSearchIndexPath = ConstructIndexFilePath(&OldBaseDir, &OldSearchLocation, Wrap0i(N->Project->ID)); + char *NewSearchIndexPath = ConstructIndexFilePath(&NewBaseDir, &NewSearchLocation, Wrap0i(N->Project->ID)); rename(OldSearchIndexPath, NewSearchIndexPath); Free(OldSearchIndexPath); Free(NewSearchIndexPath); @@ -15605,7 +16322,7 @@ DeleteDeadDBEntries(neighbourhood *N, bool *Modified) Free(NewSearchDirectory); FreeFile(&DB.File, NA); - DB.File.Path = ConstructIndexFilePath(CurrentProject->BaseDir, CurrentProject->SearchLocation, CurrentProject->ID); + DB.File.Path = ConstructIndexFilePath(&CurrentProject->BaseDir, &CurrentProject->SearchLocation, CurrentProject->ID); ReadFileIntoBuffer(&DB.File); // NOTE(matt): Could we actually catch errors (permissions?) here and bail? if(OldSearchLocation.Length > 0) @@ -15655,7 +16372,7 @@ DeleteDeadDBEntries(neighbourhood *N, bool *Modified) for(int EntryIndex = 0; EntryIndex < N->Project->EntryCount; ++EntryIndex) { db_entry *This = FirstEntry + EntryIndex; - CopyStringNoFormat(Entries[EntryIndex].ID, sizeof(Entries[EntryIndex].ID), Wrap0i(This->HMMLBaseFilename)); + ClearCopyStringNoFormatOrTerminate(Entries[EntryIndex].ID, sizeof(Entries[EntryIndex].ID), Wrap0i(This->HMMLBaseFilename)); Entries[EntryIndex].Present = FALSE; } @@ -15678,7 +16395,7 @@ DeleteDeadDBEntries(neighbourhood *N, bool *Modified) { for(int i = 0; i < N->Project->EntryCount; ++i) { - if(StringsMatch(Wrap0(Entries[i].ID), GetBaseFilename(Filename, EXT_HMML))) + if(StringsMatch(Wrap0i(Entries[i].ID), GetBaseFilename(Filename, EXT_HMML))) { Entries[i].Present = TRUE; break; @@ -15689,13 +16406,14 @@ DeleteDeadDBEntries(neighbourhood *N, bool *Modified) closedir(HMMLDirHandle); bool Deleted = FALSE; - for(int i = 0; i < N->Project->EntryCount; ++i) + int OldEntryCount = N->Project->EntryCount; + for(int i = 0; i < OldEntryCount; ++i) { if(Entries[i].Present == FALSE) { Deleted = TRUE; ResetNeighbourhood(N); - DeleteEntry(N, Wrap0(Entries[i].ID)); + DeleteEntry(N, ClashResolver, Wrap0i(Entries[i].ID)); } } @@ -15703,7 +16421,7 @@ DeleteDeadDBEntries(neighbourhood *N, bool *Modified) } rc -SyncDBWithInput(neighbourhood *N, buffers *CollationBuffers, template *BespokeTemplate) +SyncDBWithInput(neighbourhood *N, clash_resolver *ClashResolver, buffers *CollationBuffers, template *BespokeTemplate) { rc Result = RC_SUCCESS; MEM_TEST_TOP(); @@ -15745,7 +16463,7 @@ SyncDBWithInput(neighbourhood *N, buffers *CollationBuffers, template *BespokeTe } bool Deleted = FALSE; - Deleted = (DB.Metadata.File.Buffer.Size > 0 && DeleteDeadDBEntries(N, &Modified) == RC_SUCCESS); + Deleted = (DB.Metadata.File.Buffer.Size > 0 && DeleteDeadDBEntries(N, ClashResolver, &Modified) == RC_SUCCESS); // NOTE(matt): Stack-string char HMMLDir0[CurrentProject->HMMLDir.Length + 1]; @@ -15762,13 +16480,15 @@ SyncDBWithInput(neighbourhood *N, buffers *CollationBuffers, template *BespokeTe { ResetNeighbourhood(N); /* */ MEM_TEST_MID(); - /* +MEM */ Inserted |= (InsertEntry(N, CollationBuffers, BespokeTemplate, GetBaseFilename(Filename, EXT_HMML), 0) == RC_SUCCESS); + /* +MEM */ Inserted |= (InsertEntry(N, ClashResolver, CollationBuffers, BespokeTemplate, GetBaseFilename(Filename, EXT_HMML), FALSE) == RC_SUCCESS); /* */ MEM_TEST_MID(); VerifyLandmarks(N); } } closedir(HMMLDirHandle); + Inserted |= (ResolveClashes(ClashResolver, N, CollationBuffers, BespokeTemplate, FALSE) == RC_SUCCESS); + UpdateDeferredAssetChecksums(); UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts); @@ -16925,13 +17645,13 @@ PackTemplates(neighbourhood *N) } void -SyncProject(project *P, neighbourhood *N, buffers *CollationBuffers, template *BespokeTemplate, bool *Titled) +SyncProject(project *P, neighbourhood *N, clash_resolver *ClashResolver, buffers *CollationBuffers, template *BespokeTemplate, bool *Titled) { MEM_TEST_TOP(); for(int i = 0; i < P->Child.ItemCount; ++i) { /* */ MEM_TEST_MID(); - /* +MEM */ SyncProject(GetPlaceInBook(&P->Child, i), N, CollationBuffers, BespokeTemplate, Titled); + /* +MEM */ SyncProject(GetPlaceInBook(&P->Child, i), N, ClashResolver, CollationBuffers, BespokeTemplate, Titled); /* */ MEM_TEST_MID(); } @@ -16953,7 +17673,7 @@ SyncProject(project *P, neighbourhood *N, buffers *CollationBuffers, template *B Print(stderr, " ───╼\n"); /* */ MEM_TEST_MID(); - /* +MEM */ SyncDBWithInput(N, CollationBuffers, BespokeTemplate); + /* +MEM */ SyncDBWithInput(N, ClashResolver, CollationBuffers, BespokeTemplate); /* */ MEM_TEST_MID(); PushWatchHandle(P->HMMLDir, EXT_HMML, WT_HMML, P, 0); @@ -17034,11 +17754,12 @@ PrintEvent(struct inotify_event *Event, int EventIndex, int Indentation) #endif rc -InitAll(neighbourhood *Neighbourhood, buffers *CollationBuffers, template *BespokeTemplate) +InitAll(neighbourhood *Neighbourhood, clash_resolver *ClashResolver, buffers *CollationBuffers, template *BespokeTemplate) { rc Result = RC_SUCCESS; MEM_TEST_TOP(); RewindCollationBuffers(CollationBuffers); + ResetClashResolver(ClashResolver); /* */ MEM_TEST_MID(); /* +MEM */ Result = InitDB(); /* */ MEM_TEST_MID(); @@ -17061,7 +17782,7 @@ InitAll(neighbourhood *Neighbourhood, buffers *CollationBuffers, template *Bespo for(int i = 0; i < Config->Project.ItemCount; ++i) { /* */ MEM_TEST_MID(); - /* +MEM */ SyncProject(GetPlaceInBook(&Config->Project, i), Neighbourhood, CollationBuffers, BespokeTemplate, &TitledSync); + /* +MEM */ SyncProject(GetPlaceInBook(&Config->Project, i), Neighbourhood, ClashResolver, CollationBuffers, BespokeTemplate, &TitledSync); /* */ MEM_TEST_MID(); } @@ -17185,7 +17906,7 @@ GetWatchFileForEvent(struct inotify_event *Event) } void -ParseAndEitherPrintConfigOrInitAll(string ConfigPath, memory_book *TokensList, neighbourhood *N, buffers *CollationBuffers, template *BespokeTemplate) +ParseAndEitherPrintConfigOrInitAll(string ConfigPath, memory_book *TokensList, neighbourhood *N, clash_resolver *ClashResolver, buffers *CollationBuffers, template *BespokeTemplate) { Config = ParseConfig(ConfigPath, TokensList); if(Config) @@ -17196,7 +17917,7 @@ ParseAndEitherPrintConfigOrInitAll(string ConfigPath, memory_book *TokensList, n } else { - InitAll(N, CollationBuffers, BespokeTemplate); + InitAll(N, ClashResolver, CollationBuffers, BespokeTemplate); } } } @@ -17252,7 +17973,7 @@ SquashEvents(buffer *Events, int BytesRead, struct inotify_event **Event) } int -MonitorFilesystem(neighbourhood *N, buffers *CollationBuffers, template *BespokeTemplate, string ConfigPath, memory_book *TokensList) +MonitorFilesystem(neighbourhood *N, clash_resolver *ClashResolver, buffers *CollationBuffers, template *BespokeTemplate, string ConfigPath, memory_book *TokensList) { buffer Events = {}; if(ClaimBuffer(&Events, BID_INOTIFY_EVENTS, Kilobytes(4)) == RC_ARENA_FULL) { return RC_ARENA_FULL; }; @@ -17335,11 +18056,11 @@ MonitorFilesystem(neighbourhood *N, buffers *CollationBuffers, template *Bespoke string BaseFilename = GetBaseFilename(Wrap0(Event->name), WatchFile->Extension); if(Event->mask & (IN_DELETE | IN_MOVED_FROM)) { - Deleted |= (DeleteEntry(N, BaseFilename) == RC_SUCCESS); + Deleted |= (DeleteEntry(N, ClashResolver, BaseFilename) == RC_SUCCESS); } else if(Event->mask & (IN_CLOSE_WRITE | IN_MOVED_TO)) { - Inserted |= (InsertEntry(N, CollationBuffers, BespokeTemplate, BaseFilename, 0) == RC_SUCCESS); + Inserted |= (InsertEntry(N, ClashResolver, CollationBuffers, BespokeTemplate, BaseFilename, FALSE) == RC_SUCCESS); } } } break; @@ -17359,12 +18080,14 @@ MonitorFilesystem(neighbourhood *N, buffers *CollationBuffers, template *Bespoke DiscardAllAndFreeConfig(); PushWatchHandle(ConfigPath, EXT_NULL, WT_CONFIG, 0, 0); - ParseAndEitherPrintConfigOrInitAll(ConfigPath, TokensList, N, CollationBuffers, BespokeTemplate); + ParseAndEitherPrintConfigOrInitAll(ConfigPath, TokensList, N, ClashResolver, CollationBuffers, BespokeTemplate); } break; } } } + Inserted |= (ResolveClashes(ClashResolver, N, CollationBuffers, BespokeTemplate, FALSE) == RC_SUCCESS); + if(Deleted || Inserted) { UpdateDeferredAssetChecksums(); @@ -17425,6 +18148,15 @@ InitInterruptHandler(void) sigaction(SIGINT, &CleanExit, 0); } +void +InitMessageControl(void) +{ + MESSAGE_CONTROL.RepetitionCount = 1; + MESSAGE_CONTROL.Message[0] = InitBookOfStrings(512); + MESSAGE_CONTROL.Message[1] = InitBookOfStrings(512); + MESSAGE_CONTROL.DesiredMessage = &MESSAGE_CONTROL.Message[0]; + MESSAGE_CONTROL.LastMessage = &MESSAGE_CONTROL.Message[1]; +} int main(int ArgC, char **Args) @@ -17437,6 +18169,7 @@ main(int ArgC, char **Args) MEM_TEST_TOP(); InitInterruptHandler(); + InitMessageControl(); Assert(ArrayCount(BufferIDStrings) == BID_COUNT); Assert(ArrayCount(TemplateTags) == TEMPLATE_TAG_COUNT); char CommandLineArg; @@ -17501,6 +18234,7 @@ main(int ArgC, char **Args) CollationBuffers.Search.ID = BID_COLLATION_BUFFERS_SEARCH; // NOTE(matt): Allocated by SearchToBuffer() memory_book TokensList = InitBook(sizeof(tokens), 8); + clash_resolver ClashResolver = InitClashResolver(); template BespokeTemplate = {}; neighbourhood Neighbourhood = {}; @@ -17534,7 +18268,7 @@ main(int ArgC, char **Args) else { /* */ MEM_TEST_MID(); - /* +MEM */ InitAll(&Neighbourhood, &CollationBuffers, &BespokeTemplate); + /* +MEM */ InitAll(&Neighbourhood, &ClashResolver, &CollationBuffers, &BespokeTemplate); /* */ MEM_TEST_MID(); } } @@ -17552,7 +18286,7 @@ main(int ArgC, char **Args) if(inotifyInstance != -1) { - while(GlobalRunning && MonitorFilesystem(&Neighbourhood, &CollationBuffers, &BespokeTemplate, ConfigPathL, &TokensList) != RC_ARENA_FULL) + while(GlobalRunning && MonitorFilesystem(&Neighbourhood, &ClashResolver, &CollationBuffers, &BespokeTemplate, ConfigPathL, &TokensList) != RC_ARENA_FULL) { // TODO(matt): Refetch the quotes and rebuild player pages if needed // @@ -17566,7 +18300,7 @@ main(int ArgC, char **Args) // if(!(Mode & MODE_DRYRUN) && DB.Ready && Config && Config->RespectingPrivacy && time(0) - LastPrivacyCheck > Config->PrivacyCheckInterval) { - RecheckPrivacy(&Neighbourhood, &CollationBuffers, &BespokeTemplate); + RecheckPrivacy(&Neighbourhood, &ClashResolver, &CollationBuffers, &BespokeTemplate); } sleep(GLOBAL_UPDATE_INTERVAL); } diff --git a/cinera/cinera_config.c b/cinera/cinera_config.c index e615d06..3477b93 100644 --- a/cinera/cinera_config.c +++ b/cinera/cinera_config.c @@ -4532,22 +4532,18 @@ PrintLineage(string Lineage, bool AppendNewline) if(AppendNewline) { Print(stderr, "\n"); } } -int -CopyLineageToBarePtr(char *Ptr, string Lineage, bool AppendNewline) +void +ExtendLineageInBook(memory_book *M, string Lineage, bool AppendNewline) { - int Length = 0; - string Parent = StripComponentFromPath(Lineage); - Length += CopyStringCToBarePtr(CS_BLACK_BOLD, Ptr + Length, Parent); + ExtendStringCInBook(M, CS_BLACK_BOLD, Parent); if(Parent.Length > 0) { - Length += CopyStringCToBarePtr(CS_BLACK_BOLD, Ptr + Length, Wrap0("/")); + ExtendStringCInBook(M, CS_BLACK_BOLD, Wrap0("/")); } string Us = GetFinalComponent(Lineage); - Length += CopyStringCToBarePtr(CS_BLUE_BOLD, Ptr + Length, Us); - if(AppendNewline) { Length += CopyStringToBarePtr(Ptr + Length, Wrap0("\n")); } - - return Length; + ExtendStringCInBook(M, CS_BLUE_BOLD, Us); + if(AppendNewline) { ExtendStringInBook(M, Wrap0("\n")); } } uint8_t @@ -5073,7 +5069,7 @@ PositionRolesInConfig(config *C, resolution_errors *E, config_verifiers *V, scop config_pair *Position = GetPair(*Role, IDENT_POSITION); if(IsPositioned(Position)) { - int TargetPos; + int TargetPos = 0; if(Position->int64_t < 0) { TargetPos = SlotCount + Position->int64_t;