Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

blocks.gno

6.30 Kb · 216 lines
  1// Package blocks encodes a radio rotation, an ordered list of slots (an id
  2// and a duration), as strings the caller stores under its own keys: a header
  3// (slot count, total, one total per group), one group string per Size² slots
  4// (one sum per block) and one block string per Size slots. Appending is O(1),
  5// changing a slot rewrites one block, one group and the header, and finding
  6// the slot at a position in the loop scans the group totals, one group's sums
  7// and a single block: O(n/Size² + Size), flat up to about two million slots.
  8// Inside a block, ids and durations are packed as fixed-width base-64 digits
  9// (4 per id, 2 per duration): 6 bytes of deposit per slot.
 10package blocks
 11
 12import "gno.land/p/nym-alexiscolin000/gnoradio/store/v0"
 13
 14// Size is the number of slots per block.
 15const Size = 128
 16
 17// MaxID and MaxDur are what the packed digits hold.
 18const (
 19	MaxID  = 1<<24 - 1 // 16,777,215
 20	MaxDur = 1<<12 - 1 // 4095 seconds
 21)
 22
 23func checkSlot(id int, dur int64) {
 24	if id < 0 || id > MaxID {
 25		panic("blocks: id out of range")
 26	}
 27	if dur < 0 || dur > MaxDur {
 28		panic("blocks: duration out of range")
 29	}
 30}
 31
 32const groupSlots = Size * Size // slots per group
 33
 34// ---- Codec form: the same rotation over strings the caller stores ----
 35//
 36// A rotation is a header (slot count, total, one total per group), one group
 37// string per Size² slots (one sum per block) and one block string per Size
 38// slots (an id and a duration per slot). The caller keeps them under its own
 39// keys; Where names the group and block of a slot. Each function takes the
 40// strings one call touches and returns them updated, so a write costs the
 41// header, one group and one block whatever the length.
 42
 43// MaxSlots is the longest rotation a header counts.
 44const MaxSlots = 1<<24 - 1
 45
 46// Widths in base-64 digits.
 47const (
 48	nW    = 4 // header: slot count
 49	totW  = 6 // header: total, then each group's total
 50	sumW  = 4 // group: one block's sum
 51	slotW = 6 // block: id (4) and duration (2)
 52)
 53
 54// add adds d to the number of width w at position at of s.
 55func add(s string, at, w int, d int64) string {
 56	return s[:at] + store.Fixed(store.Num(s, at, w)+d, w) + s[at+w:]
 57}
 58
 59// Len is the number of slots of a rotation ("" is empty).
 60func Len(h string) int {
 61	if h == "" {
 62		return 0
 63	}
 64	return int(store.Num(h, 0, nW))
 65}
 66
 67// Total is the sum of all durations.
 68func Total(h string) int64 {
 69	if h == "" {
 70		return 0
 71	}
 72	return store.Num(h, nW, totW)
 73}
 74
 75// Where returns the group and block (counted from the start of the
 76// rotation) that hold slot i.
 77func Where(i int) (g, b int) { return i / groupSlots, i / Size }
 78
 79// fit panics unless grp and blk are slot i's group and block in a rotation
 80// of n slots (i == n: where the next slot goes, "" when new).
 81func fit(n int, grp, blk string, i int) {
 82	g, b := Where(i)
 83	inG, inB := clamp(n-g*groupSlots, groupSlots), clamp(n-b*Size, Size)
 84	if len(grp) != (inG+Size-1)/Size*sumW || len(blk) != inB*slotW {
 85		panic("blocks: wrong group or block for this slot")
 86	}
 87}
 88
 89func clamp(v, max int) int {
 90	if v < 0 {
 91		return 0
 92	}
 93	if v > max {
 94		return max
 95	}
 96	return v
 97}
 98
 99// Slot returns the id and duration of slot i, from its block.
100func Slot(blk string, i int) (int, int64) {
101	k := i % Size
102	if i < 0 || len(blk) < (k+1)*slotW {
103		panic("blocks: index out of range")
104	}
105	return int(store.Num(blk, k*slotW, 4)), store.Num(blk, k*slotW+4, 2)
106}
107
108// Append adds a slot at the end. grp and blk are those of slot Len(h)
109// (Where), "" when it starts a new one. It returns the three strings and
110// the new slot's index.
111func Append(h, grp, blk string, id int, dur int64) (string, string, string, int) {
112	checkSlot(id, dur)
113	n := Len(h)
114	if n >= MaxSlots {
115		panic("blocks: rotation full")
116	}
117	fit(n, grp, blk, n)
118	if h == "" {
119		h = store.Fixed(0, nW) + store.Fixed(0, totW)
120	}
121	if n%groupSlots == 0 {
122		h += store.Fixed(0, totW)
123	}
124	if n%Size == 0 {
125		grp += store.Fixed(0, sumW)
126	}
127	g, b := Where(n)
128	blk += store.Fixed(int64(id), 4) + store.Fixed(dur, 2)
129	grp = add(grp, b%Size*sumW, sumW, dur)
130	h = add(add(add(h, 0, nW, 1), nW, totW, dur), nW+totW+g*totW, totW, dur)
131	return h, grp, blk, n
132}
133
134// Set replaces slot i's id and duration (0 takes it out of the loop).
135func Set(h, grp, blk string, i, id int, dur int64) (string, string, string) {
136	checkSlot(id, dur)
137	n := Len(h)
138	if i < 0 || i >= n {
139		panic("blocks: index out of range")
140	}
141	fit(n, grp, blk, i)
142	_, old := Slot(blk, i)
143	k := i % Size * slotW
144	blk = blk[:k] + store.Fixed(int64(id), 4) + store.Fixed(dur, 2) + blk[k+slotW:]
145	if d := dur - old; d != 0 {
146		g, b := Where(i)
147		grp = add(grp, b%Size*sumW, sumW, d)
148		h = add(add(h, nW, totW, d), nW+totW+g*totW, totW, d)
149	}
150	return h, grp, blk
151}
152
153// PrefixBefore returns the total duration of slots 0..i-1 (0 <= i <= Len).
154func PrefixBefore(h, grp, blk string, i int) int64 {
155	n := Len(h)
156	if i < 0 || i > n {
157		panic("blocks: index out of range")
158	}
159	fit(n, grp, blk, i)
160	g, b := Where(i)
161	var p int64
162	for j := 0; j < g; j++ {
163		p += store.Num(h, nW+totW+j*totW, totW)
164	}
165	for j := 0; j < b%Size; j++ {
166		p += store.Num(grp, j*sumW, sumW)
167	}
168	for j := 0; j < i%Size; j++ {
169		p += store.Num(blk, j*slotW+4, 2)
170	}
171	return p
172}
173
174// Find is done in three steps, loading the group then the block between
175// them: FindGroup, FindBlock, FindSlot. FindGroup returns the group playing
176// at pos (0 <= pos < Total) and the position inside it; ok is false when pos
177// is out of range.
178func FindGroup(h string, pos int64) (g int, rest int64, ok bool) {
179	if pos < 0 || pos >= Total(h) {
180		return 0, 0, false
181	}
182	for at := nW + totW; at < len(h); at += totW {
183		t := store.Num(h, at, totW)
184		if pos < t {
185			return (at - nW - totW) / totW, pos, true
186		}
187		pos -= t
188	}
189	panic("blocks: bad header")
190}
191
192// FindBlock returns the block of group g playing at rest, and the position
193// inside it.
194func FindBlock(grp string, g int, rest int64) (b int, r int64) {
195	for j := 0; j*sumW < len(grp); j++ {
196		s := store.Num(grp, j*sumW, sumW)
197		if rest < s {
198			return g*Size + j, rest
199		}
200		rest -= s
201	}
202	panic("blocks: bad group")
203}
204
205// FindSlot returns the slot of block b playing at rest, its id and the
206// offset into it. Slots of duration 0 are never returned.
207func FindSlot(blk string, b int, rest int64) (slot, id int, offset int64) {
208	for k := 0; k*slotW < len(blk); k++ {
209		d := store.Num(blk, k*slotW+4, 2)
210		if rest < d {
211			return b*Size + k, int(store.Num(blk, k*slotW, 4)), rest
212		}
213		rest -= d
214	}
215	panic("blocks: bad block")
216}