Skip to content

Commit bf0e86e

Browse files
authored
Merge pull request #872 from cjbrigato/ensure-cleanstate
Makes states invalidation more robust
2 parents c3524fc + 72b28a3 commit bf0e86e

File tree

3 files changed

+39
-25
lines changed

3 files changed

+39
-25
lines changed

Context.go

+33-21
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ import (
1414
// It returns an unique value each time it is called.
1515
func GenAutoID(id string) ID {
1616
idx, ok := Context.widgetIndex[id]
17-
if !ok {
18-
Context.widgetIndex[id] = 0
19-
return ID(id)
17+
18+
if ok {
19+
idx++
2020
}
2121

22-
Context.widgetIndex[id]++
22+
Context.widgetIndex[id] = idx
2323

2424
return ID(fmt.Sprintf("%s##%d", id, idx))
2525
}
@@ -56,6 +56,10 @@ type GIUContext struct {
5656
// Indicate whether current application is running
5757
isAlive bool
5858

59+
// when dirty is true, flushStates must be called before any GetState use
60+
// when it is false, calling flushStates is noop
61+
dirty bool
62+
5963
// States will used by custom widget to store data
6064
state sync.Map
6165

@@ -100,30 +104,32 @@ func (c *GIUContext) IO() *imgui.IO {
100104
return imgui.CurrentIO()
101105
}
102106

103-
// invalidAllState should be called before rendering.
104-
// it ensures all states are marked as invalid for that moment.
105-
func (c *GIUContext) invalidAllState() {
106-
c.state.Range(func(_, v any) bool {
107-
if s, ok := v.(*state); ok {
108-
c.m.Lock()
109-
s.valid = false
110-
c.m.Unlock()
111-
}
112-
113-
return true
114-
})
107+
// SetDirty permits MasterWindow defering setting dirty states after it's render().
108+
func (c *GIUContext) SetDirty() {
109+
c.dirty = true
115110
}
116111

117-
// cleanState removes all states that were not marked as valid during rendering.
118-
// should be called after rendering.
119-
func (c *GIUContext) cleanState() {
112+
// cleanStates removes all states that were not marked as valid during rendering,
113+
// then reset said flag before new usage
114+
// should always be called before first Get/Set state use in renderloop
115+
// since afterRender() and beforeRender() are not waranted to run (see glfw_window_refresh_callback)
116+
// we call it at the very start of our render()
117+
// but just in case something happened, we also use the "dirty" flag to enforce (or avoid) flushing
118+
// on critical path.
119+
func (c *GIUContext) cleanStates() {
120+
if !c.dirty {
121+
return
122+
}
123+
120124
c.state.Range(func(k, v any) bool {
121125
if s, ok := v.(*state); ok {
122126
c.m.Lock()
123127
valid := s.valid
124128
c.m.Unlock()
125129

126-
if !valid {
130+
if valid {
131+
s.valid = false
132+
} else {
127133
c.state.Delete(k)
128134
s.data.Dispose()
129135
}
@@ -132,8 +138,8 @@ func (c *GIUContext) cleanState() {
132138
return true
133139
})
134140

135-
// Reset widgetIndex
136141
c.widgetIndex = make(map[string]int)
142+
c.dirty = false
137143
}
138144

139145
// Backend returns the imgui.backend used by the context.
@@ -143,16 +149,20 @@ func (c *GIUContext) Backend() backend.Backend[glfwbackend.GLFWWindowFlags] {
143149

144150
// SetState is a generic version of Context.SetState.
145151
func SetState[T any, PT genericDisposable[T]](c *GIUContext, id ID, data PT) {
152+
c.cleanStates()
146153
c.state.Store(id, &state{valid: true, data: data})
147154
}
148155

149156
// SetState stores data in context by id.
150157
func (c *GIUContext) SetState(id ID, data Disposable) {
158+
c.cleanStates()
151159
c.state.Store(id, &state{valid: true, data: data})
152160
}
153161

154162
// GetState is a generic version of Context.GetState.
155163
func GetState[T any, PT genericDisposable[T]](c *GIUContext, id ID) PT {
164+
c.cleanStates()
165+
156166
if s, ok := c.load(id); ok {
157167
c.m.Lock()
158168
s.valid = true
@@ -169,6 +179,8 @@ func GetState[T any, PT genericDisposable[T]](c *GIUContext, id ID) PT {
169179

170180
// GetState returns previously stored state by id.
171181
func (c *GIUContext) GetState(id ID) any {
182+
c.cleanStates()
183+
172184
if s, ok := c.load(id); ok {
173185
c.m.Lock()
174186
s.valid = true

Context_test.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,12 @@ func Test_invalidState(t *testing.T) {
8585
SetState(ctx, i, s)
8686
}
8787

88-
ctx.invalidAllState()
88+
// SetState set "valid=true" so we simulate a first end of frame before tests
89+
ctx.SetDirty()
8990

9091
_ = GetState[teststate](ctx, state2ID)
9192

92-
ctx.cleanState()
93+
ctx.SetDirty()
9394

9495
assert.NotNil(t, GetState[teststate](ctx, state2ID),
9596
"although state has been accessed during the frame, it has ben deleted by invalidAllState/cleanState")

MasterWindow.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,6 @@ func (w *MasterWindow) sizeChange(_, _ int) {
200200
}
201201

202202
func (w *MasterWindow) beforeRender() {
203-
Context.invalidAllState()
204203
Context.FontAtlas.rebuildFontAtlas()
205204

206205
// process texture load requests
@@ -214,7 +213,6 @@ func (w *MasterWindow) beforeRender() {
214213
}
215214

216215
func (w *MasterWindow) afterRender() {
217-
Context.cleanState()
218216
}
219217

220218
func (w *MasterWindow) beforeDestroy() {
@@ -223,6 +221,9 @@ func (w *MasterWindow) beforeDestroy() {
223221
}
224222

225223
func (w *MasterWindow) render() {
224+
Context.cleanStates()
225+
defer Context.SetDirty()
226+
226227
fin := w.setTheme()
227228
defer fin()
228229

0 commit comments

Comments
 (0)