Skip to content

Commit

Permalink
Beta (#219)
Browse files Browse the repository at this point in the history
* Texture support (#217)

* Feature/event loop interop (#218)

* Feature/callback messages (#220)

* fix: remove initial WaitEvent

now that we have implemented the proper event loop.
Delete the first WaitEvent

* lazy initialize go-gl/gl on first texture frame

* align BinaryMessenger with *-channels

BinaryMessenger:
SendNoReply -> Send
Send -> SendWithReply

* change the priorityQueue to specifically contain embedder.FlutterTask's

* Adds comments and renames internals. Also some re-ordering of init code. (#235)

* Addressing comments & fix pointer warning

* implement RunOnCurrentThread check using C call

* Custom error codes on the plugin side (#238)

Custom error codes on the plugin side

* rely on LockOSThread to ensure engine task are run on the same thread
  • Loading branch information
pchampio authored Sep 3, 2019
1 parent 4bb040b commit c52311f
Show file tree
Hide file tree
Showing 27 changed files with 965 additions and 165 deletions.
109 changes: 72 additions & 37 deletions application.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (

"github.com/go-flutter-desktop/go-flutter/embedder"
"github.com/go-flutter-desktop/go-flutter/internal/execpath"
"github.com/go-flutter-desktop/go-flutter/internal/tasker"
)

// Run executes a flutter application with the provided options.
Expand Down Expand Up @@ -100,7 +99,12 @@ func (a *Application) Run() error {
return errors.Errorf("invalid window mode %T", a.config.windowMode)
}

if a.config.windowInitialLocations.xpos != 0 {
glfw.WindowHint(glfw.ContextVersionMajor, 4)
glfw.WindowHint(glfw.ContextVersionMinor, 1)
glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)

if a.config.windowInitialLocation.xpos != 0 {
// To create the window at a specific position, make it initially invisible
// using the Visible window hint, set its position and then show it.
glfw.WindowHint(glfw.Visible, glfw.False)
Expand All @@ -110,12 +114,11 @@ func (a *Application) Run() error {
if err != nil {
return errors.Wrap(err, "creating glfw window")
}
glfw.DefaultWindowHints()
defer a.window.Destroy()
glfw.DefaultWindowHints()

if a.config.windowInitialLocations.xpos != 0 {
a.window.SetPos(a.config.windowInitialLocations.xpos,
a.config.windowInitialLocations.ypos)
if a.config.windowInitialLocation.xpos != 0 {
a.window.SetPos(a.config.windowInitialLocation.xpos, a.config.windowInitialLocation.ypos)
a.window.Show()
}

Expand Down Expand Up @@ -152,22 +155,18 @@ func (a *Application) Run() error {

a.engine = embedder.NewFlutterEngine()

// Create a messenger and init plugins
messenger := newMessenger(a.engine)
for _, p := range a.config.plugins {
err = p.InitPlugin(messenger)
if err != nil {
return errors.Wrap(err, "failed to initialize plugin "+fmt.Sprintf("%T", p))
}
// Create a TextureRegistry
texturer := newTextureRegistry(a.engine, a.window)

// Extra init call for plugins that satisfy the PluginGLFW interface.
if glfwPlugin, ok := p.(PluginGLFW); ok {
err = glfwPlugin.InitPluginGLFW(a.window)
if err != nil {
return errors.Wrap(err, "failed to initialize glfw plugin"+fmt.Sprintf("%T", p))
}
}
}
// Create a new eventloop
eventLoop := newEventLoop(
glfw.PostEmptyEvent, // Wakeup GLFW
a.engine.RunTask, // Flush tasks
)

// Set configuration values to engine, with fallbacks to sane defaults.
if a.config.flutterAssetsPath != "" {
a.engine.AssetsPath = a.config.flutterAssetsPath
} else {
Expand All @@ -177,7 +176,6 @@ func (a *Application) Run() error {
}
a.engine.AssetsPath = filepath.Join(filepath.Dir(execPath), "flutter_assets")
}

if a.config.icuDataPath != "" {
a.engine.IcuDataPath = a.config.icuDataPath
} else {
Expand All @@ -188,7 +186,7 @@ func (a *Application) Run() error {
a.engine.IcuDataPath = filepath.Join(filepath.Dir(execPath), "icudtl.dat")
}

// Render callbacks
// Attach GL callback functions onto the engine
a.engine.GLMakeCurrent = func() bool {
a.window.MakeContextCurrent()
return true
Expand All @@ -214,19 +212,29 @@ func (a *Application) Run() error {
a.engine.GLProcResolver = func(procName string) unsafe.Pointer {
return glfw.GetProcAddress(procName)
}
a.engine.GLExternalTextureFrameCallback = texturer.handleExternalTexture

// Attach TaskRunner callback functions onto the engine
a.engine.TaskRunnerRunOnCurrentThread = eventLoop.RunOnCurrentThread
a.engine.TaskRunnerPostTask = eventLoop.PostTask

// Attach PlatformMessage callback functions onto the engine
a.engine.PlatfromMessage = messenger.handlePlatformMessage

// Not very nice, but we can only really fix this when there's a pluggable
// renderer.
defaultTextinputPlugin.keyboardLayout = a.config.keyboardLayout

// Set the glfw window user pointer to point to the FlutterEngine so that
// callback functions may obtain the FlutterEngine from the glfw window
// user pointer.
flutterEnginePointer := uintptr(unsafe.Pointer(a.engine))
defer func() {
runtime.KeepAlive(flutterEnginePointer)
}()
a.window.SetUserPointer(unsafe.Pointer(&flutterEnginePointer))

// Start the engine
result := a.engine.Run(unsafe.Pointer(&flutterEnginePointer), a.config.vmArguments)
if result != embedder.ResultSuccess {
switch result {
Expand All @@ -240,38 +248,65 @@ func (a *Application) Run() error {
os.Exit(1)
}

defaultPlatformPlugin.glfwTasker = tasker.New()
// Register plugins
for _, p := range a.config.plugins {
err = p.InitPlugin(messenger)
if err != nil {
return errors.Wrap(err, "failed to initialize plugin "+fmt.Sprintf("%T", p))
}

m := newWindowManager()
m.forcedPixelRatio = a.config.forcePixelRatio
// Extra init call for plugins that satisfy the PluginGLFW interface.
if glfwPlugin, ok := p.(PluginGLFW); ok {
err = glfwPlugin.InitPluginGLFW(a.window)
if err != nil {
return errors.Wrap(err, "failed to initialize glfw plugin"+fmt.Sprintf("%T", p))
}
}

m.glfwRefreshCallback(a.window)
a.window.SetRefreshCallback(m.glfwRefreshCallback)
a.window.SetPosCallback(m.glfwPosCallback)
// Extra init call for plugins that satisfy the PluginTexture interface.
if texturePlugin, ok := p.(PluginTexture); ok {
err = texturePlugin.InitPluginTexture(texturer)
if err != nil {
return errors.Wrap(err, "failed to initialize texture plugin"+fmt.Sprintf("%T", p))
}
}
}

// flutter's PlatformMessage handler is registered through the dart:ui.Window
// interface. ui.Window must have at least paint one frame, before any
// platfrom message can be corectly handled by ui.Window.onPlatformMessage.
glfw.WaitEvents()
// Setup a new windowManager to handle windows pixel ratio's and pointer
// devices.
windowManager := newWindowManager(a.config.forcePixelRatio)
// force first refresh
windowManager.glfwRefreshCallback(a.window)
// Attach glfw window callbacks for refresh and position changes
a.window.SetRefreshCallback(windowManager.glfwRefreshCallback)
a.window.SetPosCallback(windowManager.glfwPosCallback)

// Attach glfw window callbacks for text input
a.window.SetKeyCallback(
func(window *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
defaultTextinputPlugin.glfwKeyCallback(window, key, scancode, action, mods)
defaultKeyeventsPlugin.sendKeyEvent(window, key, scancode, action, mods)
})
a.window.SetCharCallback(defaultTextinputPlugin.glfwCharCallback)

// Attach glfw window callback for iconification
a.window.SetIconifyCallback(defaultLifecyclePlugin.glfwIconifyCallback)

a.window.SetCursorEnterCallback(m.glfwCursorEnterCallback)
a.window.SetCursorPosCallback(m.glfwCursorPosCallback)
a.window.SetMouseButtonCallback(m.glfwMouseButtonCallback)
a.window.SetScrollCallback(m.glfwScrollCallback)
// Attach glfw window callbacks for mouse input
a.window.SetCursorEnterCallback(windowManager.glfwCursorEnterCallback)
a.window.SetCursorPosCallback(windowManager.glfwCursorPosCallback)
a.window.SetMouseButtonCallback(windowManager.glfwMouseButtonCallback)
a.window.SetScrollCallback(windowManager.glfwScrollCallback)

// Shutdown the engine if we return from this function (on purpose or panic)
defer a.engine.Shutdown()

// Handle events until the window indicates we should stop. An event may tell the window to stop, in which case
// we'll exit on next iteration.
for !a.window.ShouldClose() {
glfw.WaitEventsTimeout(0.016) // timeout to get 60fps-ish iterations
embedder.FlutterEngineFlushPendingTasksNow()
eventLoop.WaitForEvents(func(duration float64) {
glfw.WaitEventsTimeout(duration)
})
defaultPlatformPlugin.glfwTasker.ExecuteTasks()
messenger.engineTasker.ExecuteTasks()
}
Expand Down
126 changes: 112 additions & 14 deletions embedder/embedder.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ package embedder
// #include "embedder.h"
// FlutterEngineResult runFlutter(void *user_data, FlutterEngine *engine, FlutterProjectArgs * Args,
// const char *const * vmArgs, int nVmAgrs);
// FlutterEngineResult
// createMessageResponseHandle(FlutterEngine engine, void *user_data,
// FlutterPlatformMessageResponseHandle **reply);
// char** makeCharArray(int size);
// void setArrayString(char **a, char *s, int n);
// const int32_t kFlutterSemanticsNodeIdBatchEnd = -1;
// const int32_t kFlutterSemanticsCustomActionIdBatchEnd = -1;
import "C"
import (
"errors"
"fmt"
"runtime"
"runtime/debug"
"sync"
"unsafe"
Expand Down Expand Up @@ -53,6 +58,19 @@ const (
ResultEngineNotRunning Result = -1
)

// FlutterOpenGLTexture corresponds to the C.FlutterOpenGLTexture struct.
type FlutterOpenGLTexture struct {
// Target texture of the active texture unit (example GL_TEXTURE_2D)
Target uint32
// The name of the texture
Name uint32
// The texture format (example GL_RGBA8)
Format uint32
}

// FlutterTask is a type alias to C.FlutterTask
type FlutterTask = C.FlutterTask

// FlutterEngine corresponds to the C.FlutterEngine with his associated callback's method.
type FlutterEngine struct {
// Flutter Engine.
Expand All @@ -65,12 +83,17 @@ type FlutterEngine struct {
index int

// GL callback functions
GLMakeCurrent func() bool
GLClearCurrent func() bool
GLPresent func() bool
GLFboCallback func() int32
GLMakeResourceCurrent func() bool
GLProcResolver func(procName string) unsafe.Pointer
GLMakeCurrent func() bool
GLClearCurrent func() bool
GLPresent func() bool
GLFboCallback func() int32
GLMakeResourceCurrent func() bool
GLProcResolver func(procName string) unsafe.Pointer
GLExternalTextureFrameCallback func(textureID int64, width int, height int) *FlutterOpenGLTexture

// task runner interop
TaskRunnerRunOnCurrentThread func() bool
TaskRunnerPostTask func(trask FlutterTask, targetTimeNanos uint64)

// platform message callback function
PlatfromMessage func(message *PlatformMessage)
Expand Down Expand Up @@ -238,8 +261,11 @@ type PlatformMessage struct {
Channel string
Message []byte

// ResponseHandle is only set when receiving a platform message.
// https://github.com/flutter/flutter/issues/18852
// ResponseHandle is set on some recieved platform message. All
// PlatformMessage recieved with this attribute must send a response with
// `SendPlatformMessageResponse`.
// ResponseHandle can also be created from the embedder side when a
// platform(golang) message needs native callback.
ResponseHandle PlatformMessageResponseHandle
}

Expand Down Expand Up @@ -299,10 +325,82 @@ func (flu *FlutterEngine) SendPlatformMessageResponse(
return (Result)(res)
}

// FlutterEngineFlushPendingTasksNow flush tasks on a message loop not
// controlled by the Flutter engine.
//
// deprecated soon.
func FlutterEngineFlushPendingTasksNow() {
C.__FlutterEngineFlushPendingTasksNow()
// RunTask inform the engine to run the specified task.
func (flu *FlutterEngine) RunTask(task *FlutterTask) Result {
res := C.FlutterEngineRunTask(flu.Engine, task)
return (Result)(res)
}

// RegisterExternalTexture registers an external texture with a unique identifier.
func (flu *FlutterEngine) RegisterExternalTexture(textureID int64) Result {
flu.sync.Lock()
defer flu.sync.Unlock()
if flu.closed {
return ResultEngineNotRunning
}
res := C.FlutterEngineRegisterExternalTexture(flu.Engine, C.int64_t(textureID))
return (Result)(res)
}

// UnregisterExternalTexture unregisters a previous texture registration.
func (flu *FlutterEngine) UnregisterExternalTexture(textureID int64) Result {
flu.sync.Lock()
defer flu.sync.Unlock()
if flu.closed {
return ResultEngineNotRunning
}
res := C.FlutterEngineUnregisterExternalTexture(flu.Engine, C.int64_t(textureID))
return (Result)(res)
}

// MarkExternalTextureFrameAvailable marks that a new texture frame is
// available for a given texture identifier.
func (flu *FlutterEngine) MarkExternalTextureFrameAvailable(textureID int64) Result {
flu.sync.Lock()
defer flu.sync.Unlock()
if flu.closed {
return ResultEngineNotRunning
}
res := C.FlutterEngineMarkExternalTextureFrameAvailable(flu.Engine, C.int64_t(textureID))
return (Result)(res)
}

// DataCallback is a function called when a PlatformMessage response send back
// to the embedder.
type DataCallback func(binaryReply []byte)

// CreatePlatformMessageResponseHandle creates a platform message response
// handle that allows the embedder to set a native callback for a response to a
// message.
// Must be collected via `ReleasePlatformMessageResponseHandle` after the call
// to `SendPlatformMessage`.
func (flu *FlutterEngine) CreatePlatformMessageResponseHandle(callback DataCallback) (PlatformMessageResponseHandle, error) {
var responseHandle *C.FlutterPlatformMessageResponseHandle

callbackPointer := uintptr(unsafe.Pointer(&callback))
defer func() {
runtime.KeepAlive(callbackPointer)
}()

res := C.createMessageResponseHandle(flu.Engine, unsafe.Pointer(&callbackPointer), &responseHandle)
if (Result)(res) != ResultSuccess {
return 0, errors.New("failed to create a response handle")
}
return PlatformMessageResponseHandle(unsafe.Pointer(responseHandle)), nil
}

// ReleasePlatformMessageResponseHandle collects a platform message response
// handle.
func (flu *FlutterEngine) ReleasePlatformMessageResponseHandle(responseHandle PlatformMessageResponseHandle) {
cResponseHandle := (*C.FlutterPlatformMessageResponseHandle)(unsafe.Pointer(responseHandle))
res := C.FlutterPlatformMessageReleaseResponseHandle(flu.Engine, cResponseHandle)
if (Result)(res) != ResultSuccess {
fmt.Printf("go-flutter: failed to collect platform response message handle")
}
}

// FlutterEngineGetCurrentTime gets the current time in nanoseconds from the clock used by the flutter
// engine.
func FlutterEngineGetCurrentTime() uint64 {
return uint64(C.FlutterEngineGetCurrentTime())
}
Loading

0 comments on commit c52311f

Please sign in to comment.