-- Microchip-chat 0.8.7: independent, ring-buffered per-channel history. -- Original arrays and SavedVariables are migrated without discarding history. -- Vanilla WoW 1.12 / Lua 5.0 compatible. MCHistory = {} local cachedChat, cachedCombat local function invalidate() cachedChat=nil cachedCombat=nil end function MCHistory.Key(record) if not record or record.legacy then return "LEGACY" end if record.kind=="CHANNEL" then return record.channel or "CHANNEL" end return record.kind or "LEGACY" end function MCHistory.Limit(store, defaultLimit, key) local limits=store.historyLimits or {} local value=limits[key] if not value and string.sub(key or "",1,8)=="CHANNEL:" then value=limits.CHANNEL end return tonumber(value) or tonumber(defaultLimit) or 1000 end -- historyHeads[key] is the physical position of the oldest entry. The -- array itself remains densely indexed for SavedVariables serialization. -- Rotation at capacity only overwrites one slot, avoiding table.remove(1). local function getRecord(bucket, head, index) local n=table.getn(bucket) if n==0 then return nil end local physical=math.mod((head or 1)+index-2,n)+1 return bucket[physical] end function MCHistory.Get(store, key, index) local bucket=store.historyByChannel and store.historyByChannel[key] if not bucket then return nil end return getRecord(bucket,(store.historyHeads or {})[key] or 1,index) end local function normalize(store, defaultLimit, key, bucket) local n=table.getn(bucket) local limit=MCHistory.Limit(store,defaultLimit,key) local head=store.historyHeads[key] or 1 if n<=limit and (head==1 or n==limit) then return end local first=math.max(1,n-limit+1) local clean={} local i for i=first,n do table.insert(clean,getRecord(bucket,head,i)) end store.historyByChannel[key]=clean store.historyHeads[key]=1 end function MCHistory.Trim(store, defaultLimit) if type(store.historyByChannel)~="table" then return end if type(store.historyHeads)~="table" then store.historyHeads={} end local key,bucket for key,bucket in pairs(store.historyByChannel) do if type(bucket)=="table" then normalize(store,defaultLimit,key,bucket) else store.historyByChannel[key]=nil store.historyHeads[key]=nil end end invalidate() end local function migrateArray(store, records) if type(records)~="table" then return end local i,record,key,bucket for i=1,table.getn(records) do record=records[i] if type(record)=="table" then key=MCHistory.Key(record) bucket=store.historyByChannel[key] if not bucket then bucket={} store.historyByChannel[key]=bucket end table.insert(bucket,record) end end end function MCHistory.Initialize(store,defaultLimit) if type(store.historyByChannel)~="table" then store.historyByChannel={} end if type(store.historyLimits)~="table" then store.historyLimits={} end if type(store.historyHeads)~="table" then store.historyHeads={} end local key,value for key,value in pairs(store.historyLimits) do if type(key)~="string" or type(value)~="number" or value<50 or value>5000 then store.historyLimits[key]=nil else store.historyLimits[key]=math.floor(value) end end if not store.historyMigrated085 then migrateArray(store,store.entries) migrateArray(store,store.combatEntries) store.entries=nil store.combatEntries=nil store.historyMigrated085=true end -- The oldest pre-sequence legacy records may not have sequence values. -- Give them stable, unique negative IDs so cursor paging works for them. local missing=0 local bucket,i,record for key,bucket in pairs(store.historyByChannel) do if type(bucket)=="table" then for i=1,table.getn(bucket) do record=bucket[i] if type(record)=="table" and type(record.sequence)~="number" then missing=missing+1 record.sequence=-100000000+missing end end end end MCHistory.Trim(store,defaultLimit) end function MCHistory.Add(store, defaultLimit, record) local key=MCHistory.Key(record) local bucket=store.historyByChannel[key] if not bucket then bucket={} store.historyByChannel[key]=bucket end if not store.historyHeads then store.historyHeads={} end local limit=MCHistory.Limit(store,defaultLimit,key) local n=table.getn(bucket) local head=store.historyHeads[key] or 1 if n0 and (not acceptBucket or acceptBucket(key)) then n=table.getn(bucket) head=(store.historyHeads or {})[key] or 1 if pivot==nil then idx=older and n or 1 else lo=1;hi=n if older then idx=0 while lo<=hi do mid=math.floor((lo+hi)/2) record=getRecord(bucket,head,mid) sequence=record and record.sequence or 0 if sequencepivot then idx=mid;hi=mid-1 else lo=mid+1 end end end end if idx>=1 and idx<=n then table.insert(cursors,{bucket=bucket,head=head,index=idx,count=n}) end end end local gathered={} local hasMore=false local i,c,best,bestSeq,candidate,cseq while true do best=nil;bestSeq=nil for i=1,table.getn(cursors) do c=cursors[i] if c.index>=1 and c.index<=c.count then candidate=getRecord(c.bucket,c.head,c.index) cseq=candidate and candidate.sequence or 0 if bestSeq==nil or (older and cseq>bestSeq) or (not older and cseq=limit then hasMore=true;break end table.insert(gathered,candidate) end end if older then local chronological={} for i=table.getn(gathered),1,-1 do table.insert(chronological,gathered[i]) end return chronological,hasMore end return gathered,hasMore end