Dumped the mutex locks from the other controls and elaborated/asked for help in the README.

This commit is contained in:
Pietro Gagliardi 2014-03-17 14:09:09 -04:00
parent bdb26046b6
commit 0e8680c04f
9 changed files with 58 additions and 109 deletions

View File

@ -1,6 +1,23 @@
[![Build Status](https://travis-ci.org/andlabs/ui.png?branch=master)](https://travis-ci.org/andlabs/ui) [![Build Status](https://travis-ci.org/andlabs/ui.png?branch=master)](https://travis-ci.org/andlabs/ui)
# Native UI library for Go # Native UI library for Go
### CRITICAL UPDATE 17 March 2014: Due to deadlocks between resizing and setting label text once a second, Labels and Areas do not lock their internal mutex locks after their Window has been created. This seems to not have issues, and the Go race detector isn't saying anything, so IDK... Please help with this if you can. I will have to remove the mutexes from the other classes (later) to make this work properly. ### CRITICAL UPDATE 17 March 2014: Due to deadlocks between resizing and setting label text once a second (see below), all Controls no longer lock their internal mutex locks after their Window has been created. This seems to not have issues, and the Go race detector isn't saying anything, so IDK... Please help spot issues or suggest actual solutions if you can.
The issue:
- the time.Ticker ticks, which causes label.SetText() or whatever to be called; it locks its mutex
- at the same time, a resize event comes in; the resize event runs before the SetText action ever does
- the resize eventually comes to the label, whose resizing functions also try to lock the already-locked mutex
- because the drawing function is now stuck waiting for the mutex to be unlocked, the label.SetText() system-dependent operation never runs, so the mutex is never unlocked
- this fools Go's deadlock detector; it never reports anything
- changing from standard mutexes to R/W mutexes does not work
- making resizes concurrent causes resizes to become too slow to be acceptable (they trail behind the actual user resizing by a significant amount)
If you know a better way I can do things, **please** help... I'm at my wits end here
### THIS PACKAGE IS UNDER ACTIVE DEVELOPMENT. It can be used; the API is stable enough at this point, but keep in mind there may still be crashes and API changes, as suggestions are always open. If you can help, please do! Run `./test` to build a test binary `test/test` which runs a (mostly) feature-complete UI test. Run `./d32 ./test` to build a 32-bit version (you will need a cgo-enabled 32-bit go environment, and I have only tested this on Mac OS X). ### THIS PACKAGE IS UNDER ACTIVE DEVELOPMENT. It can be used; the API is stable enough at this point, but keep in mind there may still be crashes and API changes, as suggestions are always open. If you can help, please do! Run `./test` to build a test binary `test/test` which runs a (mostly) feature-complete UI test. Run `./d32 ./test` to build a 32-bit version (you will need a cgo-enabled 32-bit go environment, and I have only tested this on Mac OS X).

View File

@ -29,24 +29,22 @@ func NewButton(text string) (b *Button) {
// SetText sets the button's text. // SetText sets the button's text.
func (b *Button) SetText(text string) { func (b *Button) SetText(text string) {
b.lock.Lock()
defer b.lock.Unlock()
if b.created { if b.created {
b.sysData.setText(text) b.sysData.setText(text)
return return
} }
b.lock.Lock()
defer b.lock.Unlock()
b.initText = text b.initText = text
} }
// Text returns the button's text. // Text returns the button's text.
func (b *Button) Text() string { func (b *Button) Text() string {
b.lock.Lock()
defer b.lock.Unlock()
if b.created { if b.created {
return b.sysData.text() return b.sysData.text()
} }
b.lock.Lock()
defer b.lock.Unlock()
return b.initText return b.initText
} }
@ -64,15 +62,9 @@ func (b *Button) make(window *sysData) error {
} }
func (b *Button) setRect(x int, y int, width int, height int, winheight int) error { func (b *Button) setRect(x int, y int, width int, height int, winheight int) error {
b.lock.Lock()
defer b.lock.Unlock()
return b.sysData.setRect(x, y, width, height, winheight) return b.sysData.setRect(x, y, width, height, winheight)
} }
func (b *Button) preferredSize() (width int, height int) { func (b *Button) preferredSize() (width int, height int) {
b.lock.Lock()
defer b.lock.Unlock()
return b.sysData.preferredSize() return b.sysData.preferredSize()
} }

View File

@ -27,35 +27,32 @@ func NewCheckbox(text string) (c *Checkbox) {
// SetText sets the checkbox's text. // SetText sets the checkbox's text.
func (c *Checkbox) SetText(text string) { func (c *Checkbox) SetText(text string) {
c.lock.Lock()
defer c.lock.Unlock()
if c.created { if c.created {
c.sysData.setText(text) c.sysData.setText(text)
return return
} }
c.lock.Lock()
defer c.lock.Unlock()
c.initText = text c.initText = text
} }
// Text returns the checkbox's text. // Text returns the checkbox's text.
func (c *Checkbox) Text() string { func (c *Checkbox) Text() string {
c.lock.Lock()
defer c.lock.Unlock()
if c.created { if c.created {
return c.sysData.text() return c.sysData.text()
} }
c.lock.Lock()
defer c.lock.Unlock()
return c.initText return c.initText
} }
// Checked() returns whether or not the checkbox has been checked. // Checked() returns whether or not the checkbox has been checked.
func (c *Checkbox) Checked() bool { func (c *Checkbox) Checked() bool {
c.lock.Lock()
defer c.lock.Unlock()
if c.created { if c.created {
return c.sysData.isChecked() return c.sysData.isChecked()
} }
c.lock.Lock()
defer c.lock.Unlock()
return false return false
} }
@ -72,15 +69,9 @@ func (c *Checkbox) make(window *sysData) error {
} }
func (c *Checkbox) setRect(x int, y int, width int, height int, winheight int) error { func (c *Checkbox) setRect(x int, y int, width int, height int, winheight int) error {
c.lock.Lock()
defer c.lock.Unlock()
return c.sysData.setRect(x, y, width, height, winheight) return c.sysData.setRect(x, y, width, height, winheight)
} }
func (c *Checkbox) preferredSize() (width int, height int) { func (c *Checkbox) preferredSize() (width int, height int) {
c.lock.Lock()
defer c.lock.Unlock()
return c.sysData.preferredSize() return c.sysData.preferredSize()
} }

View File

@ -39,24 +39,20 @@ func NewEditableCombobox(items ...string) *Combobox {
// Append adds items to the end of the Combobox's list. // Append adds items to the end of the Combobox's list.
// Append will panic if something goes wrong on platforms that do not abort themselves. // Append will panic if something goes wrong on platforms that do not abort themselves.
func (c *Combobox) Append(what ...string) { func (c *Combobox) Append(what ...string) {
c.lock.Lock()
defer c.lock.Unlock()
if c.created { if c.created {
for _, s := range what { for _, s := range what {
c.sysData.append(s) c.sysData.append(s)
} }
return return
} }
c.lock.Lock()
defer c.lock.Unlock()
c.initItems = append(c.initItems, what...) c.initItems = append(c.initItems, what...)
} }
// InsertBefore inserts a new item in the Combobox before the item at the given position. It panics if the given index is out of bounds. // InsertBefore inserts a new item in the Combobox before the item at the given position. It panics if the given index is out of bounds.
// InsertBefore will also panic if something goes wrong on platforms that do not abort themselves. // InsertBefore will also panic if something goes wrong on platforms that do not abort themselves.
func (c *Combobox) InsertBefore(what string, before int) { func (c *Combobox) InsertBefore(what string, before int) {
c.lock.Lock()
defer c.lock.Unlock()
var m []string var m []string
if c.created { if c.created {
@ -66,6 +62,8 @@ func (c *Combobox) InsertBefore(what string, before int) {
c.sysData.insertBefore(what, before) c.sysData.insertBefore(what, before)
return return
} }
c.lock.Lock()
defer c.lock.Unlock()
if before < 0 || before >= len(c.initItems) { if before < 0 || before >= len(c.initItems) {
goto badrange goto badrange
} }
@ -80,9 +78,6 @@ badrange:
// Delete removes the given item from the Combobox. It panics if the given index is out of bounds. // Delete removes the given item from the Combobox. It panics if the given index is out of bounds.
func (c *Combobox) Delete(index int) { func (c *Combobox) Delete(index int) {
c.lock.Lock()
defer c.lock.Unlock()
if c.created { if c.created {
if index < 0 || index >= c.sysData.len() { if index < 0 || index >= c.sysData.len() {
goto badrange goto badrange
@ -90,6 +85,8 @@ func (c *Combobox) Delete(index int) {
c.sysData.delete(index) c.sysData.delete(index)
return return
} }
c.lock.Lock()
defer c.lock.Unlock()
if index < 0 || index >= len(c.initItems) { if index < 0 || index >= len(c.initItems) {
goto badrange goto badrange
} }
@ -101,23 +98,21 @@ badrange:
// Selection returns the current selection. // Selection returns the current selection.
func (c *Combobox) Selection() string { func (c *Combobox) Selection() string {
c.lock.Lock()
defer c.lock.Unlock()
if c.created { if c.created {
return c.sysData.text() return c.sysData.text()
} }
c.lock.Lock()
defer c.lock.Unlock()
return "" return ""
} }
// SelectedIndex returns the index of the current selection in the Combobox. It returns -1 either if no selection was made or if text was manually entered in an editable Combobox. // SelectedIndex returns the index of the current selection in the Combobox. It returns -1 either if no selection was made or if text was manually entered in an editable Combobox.
func (c *Combobox) SelectedIndex() int { func (c *Combobox) SelectedIndex() int {
c.lock.Lock()
defer c.lock.Unlock()
if c.created { if c.created {
return c.sysData.selectedIndex() return c.sysData.selectedIndex()
} }
c.lock.Lock()
defer c.lock.Unlock()
return -1 return -1
} }
@ -125,12 +120,11 @@ func (c *Combobox) SelectedIndex() int {
// //
// On platforms for which this function may return an error, it panics if one is returned. // On platforms for which this function may return an error, it panics if one is returned.
func (c *Combobox) Len() int { func (c *Combobox) Len() int {
c.lock.Lock()
defer c.lock.Unlock()
if c.created { if c.created {
return c.sysData.len() return c.sysData.len()
} }
c.lock.Lock()
defer c.lock.Unlock()
return len(c.initItems) return len(c.initItems)
} }
@ -150,15 +144,9 @@ func (c *Combobox) make(window *sysData) (err error) {
} }
func (c *Combobox) setRect(x int, y int, width int, height int, winheight int) error { func (c *Combobox) setRect(x int, y int, width int, height int, winheight int) error {
c.lock.Lock()
defer c.lock.Unlock()
return c.sysData.setRect(x, y, width, height, winheight) return c.sysData.setRect(x, y, width, height, winheight)
} }
func (c *Combobox) preferredSize() (width int, height int) { func (c *Combobox) preferredSize() (width int, height int) {
c.lock.Lock()
defer c.lock.Unlock()
return c.sysData.preferredSize() return c.sysData.preferredSize()
} }

View File

@ -121,9 +121,6 @@ func (g *Grid) make(window *sysData) error {
} }
func (g *Grid) setRect(x int, y int, width int, height int, winheight int) error { func (g *Grid) setRect(x int, y int, width int, height int, winheight int) error {
g.lock.Lock()
defer g.lock.Unlock()
max := func(a int, b int) int { max := func(a int, b int) int {
if a > b { if a > b {
return a return a
@ -188,9 +185,6 @@ func (g *Grid) setRect(x int, y int, width int, height int, winheight int) error
// filling and stretchy are ignored for preferred size calculation // filling and stretchy are ignored for preferred size calculation
func (g *Grid) preferredSize() (width int, height int) { func (g *Grid) preferredSize() (width int, height int) {
g.lock.Lock()
defer g.lock.Unlock()
max := func(a int, b int) int { max := func(a int, b int) int {
if a > b { if a > b {
return a return a

View File

@ -35,24 +35,22 @@ func NewPasswordEdit() *LineEdit {
// SetText sets the LineEdit's text. // SetText sets the LineEdit's text.
func (l *LineEdit) SetText(text string) { func (l *LineEdit) SetText(text string) {
l.lock.Lock()
defer l.lock.Unlock()
if l.created { if l.created {
l.sysData.setText(text) l.sysData.setText(text)
return return
} }
l.lock.Lock()
defer l.lock.Unlock()
l.initText = text l.initText = text
} }
// Text returns the LineEdit's text. // Text returns the LineEdit's text.
func (l *LineEdit) Text() string { func (l *LineEdit) Text() string {
l.lock.Lock()
defer l.lock.Unlock()
if l.created { if l.created {
return l.sysData.text() return l.sysData.text()
} }
l.lock.Lock()
defer l.lock.Unlock()
return l.initText return l.initText
} }
@ -70,15 +68,9 @@ func (l *LineEdit) make(window *sysData) error {
} }
func (l *LineEdit) setRect(x int, y int, width int, height int, winheight int) error { func (l *LineEdit) setRect(x int, y int, width int, height int, winheight int) error {
l.lock.Lock()
defer l.lock.Unlock()
return l.sysData.setRect(x, y, width, height, winheight) return l.sysData.setRect(x, y, width, height, winheight)
} }
func (l *LineEdit) preferredSize() (width int, height int) { func (l *LineEdit) preferredSize() (width int, height int) {
l.lock.Lock()
defer l.lock.Unlock()
return l.sysData.preferredSize() return l.sysData.preferredSize()
} }

View File

@ -40,24 +40,20 @@ func NewMultiSelListbox(items ...string) *Listbox {
// Append adds items to the end of the Listbox's list. // Append adds items to the end of the Listbox's list.
// Append will panic if something goes wrong on platforms that do not abort themselves. // Append will panic if something goes wrong on platforms that do not abort themselves.
func (l *Listbox) Append(what ...string) { func (l *Listbox) Append(what ...string) {
l.lock.Lock()
defer l.lock.Unlock()
if l.created { if l.created {
for _, s := range what { for _, s := range what {
l.sysData.append(s) l.sysData.append(s)
} }
return return
} }
l.lock.Lock()
defer l.lock.Unlock()
l.initItems = append(l.initItems, what...) l.initItems = append(l.initItems, what...)
} }
// InsertBefore inserts a new item in the Listbox before the item at the given position. It panics if the given index is out of bounds. // InsertBefore inserts a new item in the Listbox before the item at the given position. It panics if the given index is out of bounds.
// InsertBefore will also panic if something goes wrong on platforms that do not abort themselves. // InsertBefore will also panic if something goes wrong on platforms that do not abort themselves.
func (l *Listbox) InsertBefore(what string, before int) { func (l *Listbox) InsertBefore(what string, before int) {
l.lock.Lock()
defer l.lock.Unlock()
var m []string var m []string
if l.created { if l.created {
@ -67,6 +63,8 @@ func (l *Listbox) InsertBefore(what string, before int) {
l.sysData.insertBefore(what, before) l.sysData.insertBefore(what, before)
return return
} }
l.lock.Lock()
defer l.lock.Unlock()
if before < 0 || before >= len(l.initItems) { if before < 0 || before >= len(l.initItems) {
goto badrange goto badrange
} }
@ -81,9 +79,6 @@ badrange:
// Delete removes the given item from the Listbox. It panics if the given index is out of bounds. // Delete removes the given item from the Listbox. It panics if the given index is out of bounds.
func (l *Listbox) Delete(index int) { func (l *Listbox) Delete(index int) {
l.lock.Lock()
defer l.lock.Unlock()
if l.created { if l.created {
if index < 0 || index >= l.sysData.len() { if index < 0 || index >= l.sysData.len() {
goto badrange goto badrange
@ -91,6 +86,8 @@ func (l *Listbox) Delete(index int) {
l.sysData.delete(index) l.sysData.delete(index)
return return
} }
l.lock.Lock()
defer l.lock.Unlock()
if index < 0 || index >= len(l.initItems) { if index < 0 || index >= len(l.initItems) {
goto badrange goto badrange
} }
@ -102,23 +99,21 @@ badrange:
// Selection returns a list of strings currently selected in the Listbox, or an empty list if none have been selected. This list will have at most one item on a single-selection Listbox. // Selection returns a list of strings currently selected in the Listbox, or an empty list if none have been selected. This list will have at most one item on a single-selection Listbox.
func (l *Listbox) Selection() []string { func (l *Listbox) Selection() []string {
l.lock.Lock()
defer l.lock.Unlock()
if l.created { if l.created {
return l.sysData.selectedTexts() return l.sysData.selectedTexts()
} }
l.lock.Lock()
defer l.lock.Unlock()
return nil return nil
} }
// SelectedIndices returns a list of the currently selected indexes in the Listbox, or an empty list if none have been selected. This list will have at most one item on a single-selection Listbox. // SelectedIndices returns a list of the currently selected indexes in the Listbox, or an empty list if none have been selected. This list will have at most one item on a single-selection Listbox.
func (l *Listbox) SelectedIndices() []int { func (l *Listbox) SelectedIndices() []int {
l.lock.Lock()
defer l.lock.Unlock()
if l.created { if l.created {
return l.sysData.selectedIndices() return l.sysData.selectedIndices()
} }
l.lock.Lock()
defer l.lock.Unlock()
return nil return nil
} }
@ -126,12 +121,11 @@ func (l *Listbox) SelectedIndices() []int {
// //
// On platforms for which this function may return an error, it panics if one is returned. // On platforms for which this function may return an error, it panics if one is returned.
func (l *Listbox) Len() int { func (l *Listbox) Len() int {
l.lock.Lock()
defer l.lock.Unlock()
if l.created { if l.created {
return l.sysData.len() return l.sysData.len()
} }
l.lock.Lock()
defer l.lock.Unlock()
return len(l.initItems) return len(l.initItems)
} }
@ -151,15 +145,9 @@ func (l *Listbox) make(window *sysData) (err error) {
} }
func (l *Listbox) setRect(x int, y int, width int, height int, winheight int) error { func (l *Listbox) setRect(x int, y int, width int, height int, winheight int) error {
l.lock.Lock()
defer l.lock.Unlock()
return l.sysData.setRect(x, y, width, height, winheight) return l.sysData.setRect(x, y, width, height, winheight)
} }
func (l *Listbox) preferredSize() (width int, height int) { func (l *Listbox) preferredSize() (width int, height int) {
l.lock.Lock()
defer l.lock.Unlock()
return l.sysData.preferredSize() return l.sysData.preferredSize()
} }

View File

@ -30,9 +30,6 @@ func NewProgressBar() *ProgressBar {
// Otherwise, SetProgress panics. // Otherwise, SetProgress panics.
// TODO what happens if you repeatedly call SetProgress(-1)? // TODO what happens if you repeatedly call SetProgress(-1)?
func (p *ProgressBar) SetProgress(percent int) { func (p *ProgressBar) SetProgress(percent int) {
p.lock.Lock()
defer p.lock.Unlock()
if percent < -1 || percent > 100 { if percent < -1 || percent > 100 {
panic("percent value out of range") panic("percent value out of range")
} }
@ -40,6 +37,8 @@ func (p *ProgressBar) SetProgress(percent int) {
p.sysData.setProgress(percent) p.sysData.setProgress(percent)
return return
} }
p.lock.Lock()
defer p.lock.Unlock()
p.initProg = percent p.initProg = percent
} }
@ -57,15 +56,9 @@ func (p *ProgressBar) make(window *sysData) error {
} }
func (p *ProgressBar) setRect(x int, y int, width int, height int, winheight int) error { func (p *ProgressBar) setRect(x int, y int, width int, height int, winheight int) error {
p.lock.Lock()
defer p.lock.Unlock()
return p.sysData.setRect(x, y, width, height, winheight) return p.sysData.setRect(x, y, width, height, winheight)
} }
func (p *ProgressBar) preferredSize() (width int, height int) { func (p *ProgressBar) preferredSize() (width int, height int) {
p.lock.Lock()
defer p.lock.Unlock()
return p.sysData.preferredSize() return p.sysData.preferredSize()
} }

View File

@ -78,9 +78,6 @@ func (s *Stack) make(window *sysData) error {
} }
func (s *Stack) setRect(x int, y int, width int, height int, winheight int) error { func (s *Stack) setRect(x int, y int, width int, height int, winheight int) error {
s.lock.Lock()
defer s.lock.Unlock()
var stretchywid, stretchyht int var stretchywid, stretchyht int
if len(s.controls) == 0 { // do nothing if there's nothing to do if len(s.controls) == 0 { // do nothing if there's nothing to do
@ -138,9 +135,6 @@ func (s *Stack) setRect(x int, y int, width int, height int, winheight int) erro
// The preferred size of a Stack is the sum of the preferred sizes of non-stretchy controls + (the number of stretchy controls * the largest preferred size among all stretchy controls). // The preferred size of a Stack is the sum of the preferred sizes of non-stretchy controls + (the number of stretchy controls * the largest preferred size among all stretchy controls).
func (s *Stack) preferredSize() (width int, height int) { func (s *Stack) preferredSize() (width int, height int) {
s.lock.Lock()
defer s.lock.Unlock()
max := func(a int, b int) int { max := func(a int, b int) int {
if a > b { if a > b {
return a return a