Merge pull request #70 from rcalixte/goroutine6

Add goroutine6
This commit is contained in:
mappu 2024-12-30 17:12:49 +13:00 committed by GitHub
commit 12618a309c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 160 additions and 0 deletions

1
.gitignore vendored
View File

@ -20,6 +20,7 @@ cmd/genbindings/genbindings
cmd/miqt-uic/miqt-uic
cmd/miqt-rcc/miqt-rcc
examples/goroutine6/goroutine6
examples/helloworld/helloworld
examples/helloworld6/helloworld6
examples/mdoutliner/mdoutliner

View File

@ -94,6 +94,8 @@ Qt class inherited types are projected as a Go embedded struct. For example, to
The Go runtime migrates goroutines between OS threads, but Qt expects fixed OS threads to be used for each QObject. When you first call `qt.NewQApplication` in MIQT, that will be considered the [Qt main thread](https://doc.qt.io/qt-6/thread-basics.html#gui-thread-and-worker-thread) and will automatically signal the Go runtime to bind to a fixed OS thread using `runtime.LockOSThread()`.
- When accessing Qt objects from inside another goroutine, it's safest to use `(qt6/mainthread).Wait()` to access the Qt objects from Qt's main thread.
Some C++ idioms that were difficult to project were omitted from the binding. But, this can be improved in the future.
### Q6. Can I use Qt Designer and the Qt Resource system?

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,57 @@
package main
import (
"fmt"
"os"
"runtime"
"time"
qt "github.com/mappu/miqt/qt6"
"github.com/mappu/miqt/qt6/mainthread"
)
func main() {
threadcount := runtime.GOMAXPROCS(0)
qt.NewQApplication(os.Args)
window := qt.NewQMainWindow2()
window.QWidget.SetFixedSize2(250, 50*(threadcount+1))
window.QWidget.SetWindowTitle("goroutine Example")
widget := qt.NewQWidget(window.QWidget)
var layout = qt.NewQVBoxLayout2()
widget.SetLayout(layout.QLayout)
window.SetCentralWidget(widget)
labels := make([]*qt.QLabel, threadcount)
for i := range labels {
label := qt.NewQLabel(window.QWidget)
label.SetAlignment(qt.AlignCenter)
widget.Layout().AddWidget(label.QWidget)
labels[i] = label
}
button := qt.NewQPushButton5("start!", window.QWidget)
button.OnClicked1(func(bool) {
button.SetDisabled(true)
for i, label := range labels {
go func(index int, qlabel *qt.QLabel) {
var tick int
for range time.NewTicker(time.Duration((index+1)*25) * time.Millisecond).C {
tick++
// time.Sleep(50 * time.Millisecond)
mainthread.Wait(func() {
qlabel.SetText(fmt.Sprintf("%v %v", tick, time.Now().UTC().Format("15:04:05.0000")))
})
}
}(i, label)
}
})
widget.Layout().AddWidget(button.QWidget)
window.Show()
qt.QApplication_Exec()
}

View File

@ -0,0 +1,13 @@
#include <QMetaObject>
#include <QCoreApplication>
#ifndef _Bool
#define _Bool bool
#endif
#include "_cgo_export.h"
void mainthread_exec(intptr_t cb) {
QMetaObject::invokeMethod(qApp, [=]{
mainthread_exec_handle(cb);
}, Qt::QueuedConnection);
}

View File

@ -0,0 +1,69 @@
package mainthread
import (
"sync"
"runtime/cgo"
)
/*
#cgo pkg-config: Qt6Core
#include "mainthread.h"
*/
import "C"
// Start runs the callback in the main Qt thread. You should use this whenever
// accessing the main Qt GUI from inside a goroutine.
// This function is non-blocking.
func Start(gofunc func()) {
h := cgo.NewHandle(gofunc)
C.mainthread_exec(C.intptr_t(h))
}
// Wait runs the callback in the main Qt thread. You should use this whenever
// accessing the main Qt GUI from inside a goroutine.
// The call blocks until the callback is executed in the main thread's eventloop.
func Wait(gofunc func()) {
// It's possible to use Qt::BlockingQueuedConnection to implement the
// blocking, but it has a deadlock risk
var wg sync.WaitGroup
wg.Add(1)
outerfunc := func() {
gofunc()
wg.Done()
}
Start(outerfunc)
wg.Wait()
}
func Wait2[T any](gofunc func() T) (ret T) {
outerfunc := func() {
ret = gofunc()
}
Wait(outerfunc)
return ret
}
func Wait3[T any](gofunc func() (T, error)) (ret T, err error) {
outerfunc := func() {
ret, err = gofunc()
}
Wait(outerfunc)
return ret, err
}
//export mainthread_exec_handle
func mainthread_exec_handle(u uintptr) {
h := cgo.Handle(u)
gofunc, ok := h.Value().(func())
if !ok {
panic("miqt: callback of non-callback type (heap corruption?)")
}
gofunc()
// Free handle after use
h.Delete()
}

View File

@ -0,0 +1,18 @@
#pragma once
#ifndef QT_MAINTHREAD_H
#define QT_MAINTHREAD_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
void mainthread_exec(intptr_t cb);
#ifdef __cplusplus
}
#endif
#endif