Skip to content

Commit

Permalink
Provide a Painter to Paint hooks.
Browse files Browse the repository at this point in the history
  • Loading branch information
niemeyer committed Jan 27, 2014
1 parent 8616aa0 commit a549c79
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 27 deletions.
10 changes: 6 additions & 4 deletions all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,13 @@ type RectType struct {
PaintCount int
}

func (r *RectType) Paint() {
func (r *RectType) Paint(p *qml.Painter) {
r.PaintCount++

// TODO Dynamically compute this.
width := gl.Float(100)
height := gl.Float(100)
obj := p.Object()

width := gl.Float(obj.Int("width"))
height := gl.Float(obj.Int("height"))

gl.Color3f(1.0, 0.0, 0.0)
gl.Begin(gl.QUADS)
Expand Down Expand Up @@ -988,6 +989,7 @@ func (s *S) TestTable(c *C) {

rect := t.Rect
goRectValue = &rect
rect.engine = s.engine

testData := TestData{
C: c,
Expand Down
34 changes: 27 additions & 7 deletions bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,14 @@ var (
guiLock = 0
guiLoopReady sync.Mutex
guiLoopRef uintptr
guiPaintRef uintptr
)

// gui runs f in the main GUI thread and waits for f to return.
func gui(f func()) {
if tref.Ref() == guiLoopRef {
// Already within the GUI thread. Attempting to wait would deadlock.
ref := tref.Ref()
if ref == guiLoopRef || ref == atomic.LoadUintptr(&guiPaintRef) {
// Already within the GUI or render threads. Attempting to wait would deadlock.
f()
return
}
Expand Down Expand Up @@ -225,11 +227,17 @@ func wrapGoValue(engine *Engine, gvalue interface{}, owner valueOwner) (cvalue u
panic("cannot hand pointer of pointer to QML logic; use a simple pointer instead")
}

painting := tref.Ref() == atomic.LoadUintptr(&guiPaintRef)

prev, ok := engine.values[gvalue]
if ok && (prev.owner == owner || owner != cppOwner) {
if ok && (prev.owner == owner || owner != cppOwner || painting) {
return prev.cvalue
}

if painting {
panic("cannot allocate new objects while painting")
}

parent := nilPtr
if owner == cppOwner {
parent = engine.addr
Expand All @@ -246,7 +254,7 @@ func wrapGoValue(engine *Engine, gvalue interface{}, owner valueOwner) (cvalue u
} else {
engine.values[gvalue] = fold
}
//fmt.Printf("[DEBUG] value alive (wrapped): %x/%#v\n", fold.cvalue, fold.gvalue)
//fmt.Printf("[DEBUG] value alive (wrapped): cvalue=%x gvalue=%x/%#v\n", fold.cvalue, addrOf(fold.gvalue), fold.gvalue)
stats.valuesAlive(+1)
C.engineSetContextForObject(engine.addr, fold.cvalue)
switch owner {
Expand All @@ -258,6 +266,10 @@ func wrapGoValue(engine *Engine, gvalue interface{}, owner valueOwner) (cvalue u
return fold.cvalue
}

func addrOf(gvalue interface{}) uintptr {
return reflect.ValueOf(gvalue).Pointer()
}

// typeNew holds fold values that are created by registered types.
// These values are special in two senses: first, they don't have a
// reference to an engine before they are used in a context that can
Expand All @@ -281,7 +293,7 @@ func hookGoValueTypeNew(cvalue unsafe.Pointer, specp unsafe.Pointer) (foldp unsa
owner: jsOwner,
}
typeNew[fold] = true
//fmt.Printf("[DEBUG] value alive (type-created): %x/%#v\n", fold.cvalue, fold.gvalue)
//fmt.Printf("[DEBUG] value alive (type-created): cvalue=%x gvalue=%x/%#v\n", fold.cvalue, addrOf(fold.gvalue), fold.gvalue)
stats.valuesAlive(+1)
return unsafe.Pointer(fold)
}
Expand Down Expand Up @@ -324,7 +336,7 @@ func hookGoValueDestroyed(enginep unsafe.Pointer, foldp unsafe.Pointer) {
}
}
}
//fmt.Printf("[DEBUG] value destroyed: %x/%#v\n", fold.cvalue, fold.gvalue)
//fmt.Printf("[DEBUG] value destroyed: cvalue=%x gvalue=%x/%#v\n", fold.cvalue, addrOf(fold.gvalue), fold.gvalue)
stats.valuesAlive(-1)
}

Expand Down Expand Up @@ -481,8 +493,16 @@ func hookGoValuePaint(enginep, foldp unsafe.Pointer, reflectIndex C.intptr_t) {
fold := ensureEngine(enginep, foldp)
v := reflect.ValueOf(fold.gvalue)

// The main GUI thread is mutex-locked while paint methods are called,
// so no two paintings should be happening at the same time.
atomic.StoreUintptr(&guiPaintRef, tref.Ref())

painter := &Painter{fold.engine, &Common{fold.cvalue, fold.engine}}

method := v.Method(int(reflectIndex))
method.Call(nil)
method.Call([]reflect.Value{reflect.ValueOf(painter)})

atomic.StoreUintptr(&guiPaintRef, 0)
}

func ensureEngine(enginep, foldp unsafe.Pointer) *valueFold {
Expand Down
5 changes: 3 additions & 2 deletions datatype.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var (
typeIface = reflect.TypeOf(new(interface{})).Elem()
typeRGBA = reflect.TypeOf(color.RGBA{})
typeObjSlice = reflect.TypeOf([]Object(nil))
typePainter = reflect.TypeOf(&Painter{})
)

func init() {
Expand Down Expand Up @@ -140,7 +141,7 @@ func unpackDataValue(dvalue *C.DataValue, engine *Engine) interface{} {
case C.DTInvalid:
return nil
case C.DTObject:
// TODO Would be good to preserve identity on the Go side.
// TODO Would be good to preserve identity on the Go side. See Engine.ObjectOf as well.
return &Common{
engine: engine,
addr: *(*unsafe.Pointer)(datap),
Expand Down Expand Up @@ -316,7 +317,7 @@ func typeInfo(v interface{}) *C.GoTypeInfo {
membersi += 1
mnamesi += uintptr(len(method.Name)) + 1

if method.Name == "Paint" && memberInfo.numIn == 0 && memberInfo.numOut == 0 {
if method.Name == "Paint" && memberInfo.numIn == 1 && memberInfo.numOut == 0 && method.Type.In(1) == typePainter {
typeInfo.paint = memberInfo
}
}
Expand Down
50 changes: 36 additions & 14 deletions qml.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,30 @@ func (e *Engine) Context() *Context {
return &ctx
}

// TODO ObjectOf is probably still worth it, but turned out unnecessary
// for GL functionality. Test it properly before introducing it.

// ObjectOf returns the QML Object representation of the provided Go value
// within the e engine.
//func (e *Engine) ObjectOf(value interface{}) Object {
// // TODO Would be good to preserve identity on the Go side. See unpackDataValue as well.
// return &Common{
// engine: e,
// addr: wrapGoValue(e, value, cppOwner),
// }
//}

// Painter is provided to Paint methods on Go types that have displayable content.
type Painter struct {
enigne *Engine
obj Object
}

// Object returns the underlying object being painted.
func (p *Painter) Object() Object {
return p.obj
}

// AddImageProvider registers f to be called when an image is requested by QML code
// with the specified provider identifier. It is a runtime error to register the same
// provider identifier multiple times.
Expand Down Expand Up @@ -319,8 +343,6 @@ func (ctx *Context) Var(name string) interface{} {

// TODO Context.Spawn() => Context

// TODO engine.ObjectOf(&value) => *Common for the Go value

// Object is the common interface implemented by all QML types.
//
// See the documentation of Common for details about this interface.
Expand Down Expand Up @@ -786,6 +808,18 @@ func (win *Window) Wait() {
m.Lock()
}

var waitingWindows = make(map[unsafe.Pointer]*sync.Mutex)

//export hookWindowHidden
func hookWindowHidden(addr unsafe.Pointer) {
m, ok := waitingWindows[addr]
if !ok {
panic("window is not waiting")
}
delete(waitingWindows, addr)
m.Unlock()
}

// Snapshot returns an image with the visible contents of the window.
// The main GUI thread is paused while the data is being acquired.
func (win *Window) Snapshot() image.Image {
Expand Down Expand Up @@ -818,18 +852,6 @@ func (win *Window) Snapshot() image.Image {
return image
}

var waitingWindows = make(map[unsafe.Pointer]*sync.Mutex)

//export hookWindowHidden
func hookWindowHidden(addr unsafe.Pointer) {
m, ok := waitingWindows[addr]
if !ok {
panic("window is not waiting")
}
delete(waitingWindows, addr)
m.Unlock()
}

// TypeSpec holds the specification of a QML type that is backed by Go logic.
//
// The type specification must be registered with the RegisterTypes function
Expand Down

0 comments on commit a549c79

Please sign in to comment.