counter.gno
3.68 Kb · 143 lines
1// Package counter implements "Counter Club": a single shared integer counter
2// with a rolling history of the last 20 mutations. Anyone may Inc, Dec, or
3// Reset the value; every change records the caller, the delta applied, the
4// resulting value, and the block height it happened at.
5package counter
6
7import (
8 "chain"
9 "chain/runtime"
10 "chain/runtime/unsafe"
11 "strconv"
12 "strings"
13)
14
15// maxHistory is the number of recent changes kept in the log.
16const maxHistory = 20
17
18// change is a single recorded mutation of the counter.
19type change struct {
20 caller address // who triggered the change
21 delta int // how much the value moved (+1, -1, or reset amount)
22 value int // the resulting counter value after the change
23 height int64 // block height at which it occurred
24}
25
26// value is the current shared counter.
27var value int
28
29// total counts every mutation ever applied (not capped, unlike history).
30var total int
31
32// history holds the last maxHistory changes, oldest first.
33var history []change
34
35// record appends a change to the history, trimming to the last maxHistory.
36func record(delta, newValue int) {
37 c := change{
38 caller: unsafe.PreviousRealm().Address(),
39 delta: delta,
40 value: newValue,
41 height: runtime.ChainHeight(),
42 }
43 history = append(history, c)
44 if len(history) > maxHistory {
45 history = history[len(history)-maxHistory:]
46 }
47 total++
48 chain.Emit(
49 "CounterChange",
50 "delta", strconv.Itoa(delta),
51 "value", strconv.Itoa(newValue),
52 )
53}
54
55// Inc adds one to the shared counter and logs the change.
56func Inc(cur realm) int {
57 value++
58 record(1, value)
59 return value
60}
61
62// Dec subtracts one from the shared counter and logs the change.
63func Dec(cur realm) int {
64 value--
65 record(-1, value)
66 return value
67}
68
69// Reset sets the shared counter back to zero and logs the change. The recorded
70// delta is the amount required to bring the previous value to zero.
71func Reset(cur realm) int {
72 delta := -value
73 value = 0
74 record(delta, value)
75 return value
76}
77
78// Value returns the current counter value (read-only helper).
79func Value() int {
80 return value
81}
82
83// Total returns the total number of mutations ever applied.
84func Total() int {
85 return total
86}
87
88// Render produces a Markdown view: the big current value, the total number of
89// changes, and a table of the most recent changes (newest first).
90func Render(path string) string {
91 var b strings.Builder
92
93 b.WriteString("# Counter Club\n\n")
94 b.WriteString("A shared counter anyone can Inc, Dec, or Reset. ")
95 b.WriteString("The last ")
96 b.WriteString(strconv.Itoa(maxHistory))
97 b.WriteString(" changes are logged below.\n\n")
98
99 // Big current value.
100 b.WriteString("## Current value\n\n")
101 b.WriteString("# ")
102 b.WriteString(strconv.Itoa(value))
103 b.WriteString("\n\n")
104
105 b.WriteString("**Total changes:** ")
106 b.WriteString(strconv.Itoa(total))
107 b.WriteString("\n\n")
108
109 // Recent history, newest first.
110 b.WriteString("## Recent history\n\n")
111 if len(history) == 0 {
112 b.WriteString("_No changes yet — be the first to count!_\n")
113 return b.String()
114 }
115
116 b.WriteString("| # | Caller | Delta | New value | Height |\n")
117 b.WriteString("|---|--------|-------|-----------|--------|\n")
118 n := len(history)
119 for i := n - 1; i >= 0; i-- {
120 c := history[i]
121 b.WriteString("| ")
122 b.WriteString(strconv.Itoa(n - i))
123 b.WriteString(" | ")
124 b.WriteString(c.caller.String())
125 b.WriteString(" | ")
126 b.WriteString(signed(c.delta))
127 b.WriteString(" | ")
128 b.WriteString(strconv.Itoa(c.value))
129 b.WriteString(" | ")
130 b.WriteString(strconv.FormatInt(c.height, 10))
131 b.WriteString(" |\n")
132 }
133
134 return b.String()
135}
136
137// signed formats an int with an explicit leading sign for non-negative values.
138func signed(n int) string {
139 if n >= 0 {
140 return "+" + strconv.Itoa(n)
141 }
142 return strconv.Itoa(n)
143}