forked from mbk-lab/rui_orig
2
0
Fork 0

Updated wasmBridge

This commit is contained in:
anoshenko 2022-11-02 21:05:55 +03:00
parent 4e363d03a5
commit c0f60e7bdd
10 changed files with 696 additions and 648 deletions

View File

@ -78,8 +78,8 @@ func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, app.getStartPage())
case "/ws":
if brige := CreateSocketBrige(w, req); brige != nil {
go app.socketReader(brige)
if bridge := CreateSocketBridge(w, req); bridge != nil {
go app.socketReader(bridge)
}
default:
@ -96,12 +96,12 @@ func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
}
func (app *application) socketReader(brige webBrige) {
func (app *application) socketReader(bridge webBridge) {
var session Session
events := make(chan DataObject, 1024)
for {
message, ok := brige.readMessage()
message, ok := bridge.readMessage()
if !ok {
events <- NewDataObject("disconnect")
return
@ -116,28 +116,28 @@ func (app *application) socketReader(brige webBrige) {
switch command {
case "startSession":
answer := ""
if session, answer = app.startSession(obj, events, brige); session != nil {
if !brige.writeMessage(answer) {
if session, answer = app.startSession(obj, events, bridge); session != nil {
if !bridge.writeMessage(answer) {
return
}
session.onStart()
go sessionEventHandler(session, events, brige)
go sessionEventHandler(session, events, bridge)
}
case "reconnect":
if sessionText, ok := obj.PropertyValue("session"); ok {
if sessionID, err := strconv.Atoi(sessionText); err == nil {
if session = app.sessions[sessionID]; session != nil {
session.setBrige(events, brige)
session.setBridge(events, bridge)
answer := allocStringBuilder()
defer freeStringBuilder(answer)
session.writeInitScript(answer)
if !brige.writeMessage(answer.String()) {
if !bridge.writeMessage(answer.String()) {
return
}
session.onReconnect()
go sessionEventHandler(session, events, brige)
go sessionEventHandler(session, events, bridge)
return
}
DebugLogF("Session #%d not exists", sessionID)
@ -149,12 +149,12 @@ func (app *application) socketReader(brige webBrige) {
}
answer := ""
if session, answer = app.startSession(obj, events, brige); session != nil {
if !brige.writeMessage(answer) {
if session, answer = app.startSession(obj, events, bridge); session != nil {
if !bridge.writeMessage(answer) {
return
}
session.onStart()
go sessionEventHandler(session, events, brige)
go sessionEventHandler(session, events, bridge)
}
case "answer":
@ -173,7 +173,7 @@ func (app *application) socketReader(brige webBrige) {
}
}
func sessionEventHandler(session Session, events chan DataObject, brige webBrige) {
func sessionEventHandler(session Session, events chan DataObject, bridge webBridge) {
for {
data := <-events
@ -185,7 +185,7 @@ func sessionEventHandler(session Session, events chan DataObject, brige webBrige
case "session-close":
session.onFinish()
session.App().removeSession(session.ID())
brige.close()
bridge.close()
default:
session.handleEvent(command, data)
@ -193,13 +193,13 @@ func sessionEventHandler(session Session, events chan DataObject, brige webBrige
}
}
func (app *application) startSession(params DataObject, events chan DataObject, brige webBrige) (Session, string) {
func (app *application) startSession(params DataObject, events chan DataObject, bridge webBridge) (Session, string) {
if app.createContentFunc == nil {
return nil, ""
}
session := newSession(app, app.nextSessionID(), "", params)
session.setBrige(events, brige)
session.setBridge(events, bridge)
if !session.setContent(app.createContentFunc(session)) {
return nil, ""
}

View File

@ -17,7 +17,7 @@ type wasmApp struct {
params AppParams
createContentFunc func(Session) SessionContent
session Session
brige webBrige
bridge webBridge
close chan DataObject
}
@ -36,28 +36,28 @@ func (app *wasmApp) handleMessage(this js.Value, args []js.Value) any {
/*
case "startSession":
answer := ""
if session, answer = app.startSession(obj, events, brige); session != nil {
if !brige.writeMessage(answer) {
if session, answer = app.startSession(obj, events, bridge); session != nil {
if !bridge.writeMessage(answer) {
return
}
session.onStart()
go sessionEventHandler(session, events, brige)
go sessionEventHandler(session, events, bridge)
}
case "reconnect":
if sessionText, ok := obj.PropertyValue("session"); ok {
if sessionID, err := strconv.Atoi(sessionText); err == nil {
if session = app.sessions[sessionID]; session != nil {
session.setBrige(events, brige)
session.setBridge(events, bridge)
answer := allocStringBuilder()
defer freeStringBuilder(answer)
session.writeInitScript(answer)
if !brige.writeMessage(answer.String()) {
if !bridge.writeMessage(answer.String()) {
return
}
session.onReconnect()
go sessionEventHandler(session, events, brige)
go sessionEventHandler(session, events, bridge)
return
}
DebugLogF("Session #%d not exists", sessionID)
@ -69,12 +69,12 @@ func (app *wasmApp) handleMessage(this js.Value, args []js.Value) any {
}
answer := ""
if session, answer = app.startSession(obj, events, brige); session != nil {
if !brige.writeMessage(answer) {
if session, answer = app.startSession(obj, events, bridge); session != nil {
if !bridge.writeMessage(answer) {
return
}
session.onStart()
go sessionEventHandler(session, events, brige)
go sessionEventHandler(session, events, bridge)
}
case "disconnect":
@ -84,7 +84,7 @@ func (app *wasmApp) handleMessage(this js.Value, args []js.Value) any {
case "session-close":
session.onFinish()
session.App().removeSession(session.ID())
brige.close()
bridge.close()
*/
case "answer":
@ -109,7 +109,7 @@ func (app *wasmApp) removeSession(id int) {
func (app *wasmApp) createSession() Session {
session := newSession(app, 0, "", ParseDataText(js.Global().Call("sessionInfo", "").String()))
session.setBrige(app.close, app.brige)
session.setBridge(app.close, app.bridge)
session.setContent(app.createContentFunc(session))
return session
}
@ -196,7 +196,7 @@ func (app *wasmApp) init(params AppParams) {
body.Call("appendChild", div)
if params.TitleColor != 0 {
app.brige.callFunc("setTitleColor", params.TitleColor.cssString())
app.bridge.callFunc("setTitleColor", params.TitleColor.cssString())
}
}
@ -212,7 +212,7 @@ func StartApp(addr string, createContentFunc func(Session) SessionContent, param
app := new(wasmApp)
app.createContentFunc = createContentFunc
app.brige = createWasmBrige()
app.bridge = createWasmBridge()
app.init(params)
<-app.close

View File

@ -1799,13 +1799,15 @@ function appendStyles(styles) {
function getCanvasContext(elementId) {
const canvas = document.getElementById(elementId)
const ctx = canvas.getContext('2d');
const dpr = window.devicePixelRatio || 1;
//var gradient;
//var path;
//var img;
ctx.canvas.width = dpr * canvas.clientWidth;
ctx.canvas.height = dpr * canvas.clientHeight;
ctx.scale(dpr, dpr);
return ctx;
if (canvas) {
const ctx = canvas.getContext('2d');
if (ctx) {
const dpr = window.devicePixelRatio || 1;
ctx.canvas.width = dpr * canvas.clientWidth;
ctx.canvas.height = dpr * canvas.clientHeight;
ctx.scale(dpr, dpr);
return ctx;
}
}
return null;
}

View File

@ -486,6 +486,19 @@ func (canvas *canvasData) SetLineCap(cap int) {
}
func (canvas *canvasData) SetLineDash(dash []float64, offset float64) {
/*buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
lead := '['
for _, val := range dash {
buffer.WriteRune(lead)
lead = ','
buffer.WriteString(fmt.Sprintf("%g", val))
}
buffer.WriteRune(']')
canvas.session.callCanvasFunc("setLineDash", buffer.String())
*/
canvas.session.callCanvasFunc("setLineDash", dash)
if offset >= 0 {
canvas.session.updateCanvasProperty("lineDashOffset", offset)

View File

@ -7,7 +7,7 @@ import (
"strings"
)
type webBrige interface {
type webBridge interface {
startUpdateScript(htmlID string) bool
finishUpdateScript(htmlID string)
callFunc(funcName string, args ...any) bool
@ -111,7 +111,7 @@ type Session interface {
nextViewID() string
styleProperty(styleTag, property string) any
setBrige(events chan DataObject, brige webBrige)
setBridge(events chan DataObject, bridge webBridge)
writeInitScript(writer *strings.Builder)
callFunc(funcName string, args ...any)
updateInnerHTML(htmlID, html string)
@ -175,7 +175,7 @@ type sessionData struct {
ignoreUpdates bool
popups *popupManager
images *imageManager
brige webBrige
bridge webBridge
events chan DataObject
animationCounter int
animationCSS string
@ -219,9 +219,9 @@ func (session *sessionData) ID() int {
return session.sessionID
}
func (session *sessionData) setBrige(events chan DataObject, brige webBrige) {
func (session *sessionData) setBridge(events chan DataObject, bridge webBridge) {
session.events = events
session.brige = brige
session.bridge = bridge
}
func (session *sessionData) close() {
@ -313,7 +313,7 @@ func (session *sessionData) reload() {
}
func (session *sessionData) ignoreViewUpdates() bool {
return session.brige == nil || session.ignoreUpdates
return session.bridge == nil || session.ignoreUpdates
}
func (session *sessionData) setIgnoreViewUpdates(ignore bool) {
@ -351,8 +351,8 @@ func (session *sessionData) imageManager() *imageManager {
}
func (session *sessionData) callFunc(funcName string, args ...any) {
if session.brige != nil {
session.brige.callFunc(funcName, args...)
if session.bridge != nil {
session.bridge.callFunc(funcName, args...)
} else {
ErrorLog("No connection")
}
@ -360,8 +360,8 @@ func (session *sessionData) callFunc(funcName string, args ...any) {
func (session *sessionData) updateInnerHTML(htmlID, html string) {
if !session.ignoreViewUpdates() {
if session.brige != nil {
session.brige.updateInnerHTML(htmlID, html)
if session.bridge != nil {
session.bridge.updateInnerHTML(htmlID, html)
} else {
ErrorLog("No connection")
}
@ -370,8 +370,8 @@ func (session *sessionData) updateInnerHTML(htmlID, html string) {
func (session *sessionData) appendToInnerHTML(htmlID, html string) {
if !session.ignoreViewUpdates() {
if session.brige != nil {
session.brige.appendToInnerHTML(htmlID, html)
if session.bridge != nil {
session.bridge.appendToInnerHTML(htmlID, html)
} else {
ErrorLog("No connection")
}
@ -379,90 +379,90 @@ func (session *sessionData) appendToInnerHTML(htmlID, html string) {
}
func (session *sessionData) updateCSSProperty(htmlID, property, value string) {
if !session.ignoreViewUpdates() && session.brige != nil {
session.brige.updateCSSProperty(htmlID, property, value)
if !session.ignoreViewUpdates() && session.bridge != nil {
session.bridge.updateCSSProperty(htmlID, property, value)
}
}
func (session *sessionData) updateProperty(htmlID, property string, value any) {
if !session.ignoreViewUpdates() && session.brige != nil {
session.brige.updateProperty(htmlID, property, value)
if !session.ignoreViewUpdates() && session.bridge != nil {
session.bridge.updateProperty(htmlID, property, value)
}
}
func (session *sessionData) removeProperty(htmlID, property string) {
if !session.ignoreViewUpdates() && session.brige != nil {
session.brige.removeProperty(htmlID, property)
if !session.ignoreViewUpdates() && session.bridge != nil {
session.bridge.removeProperty(htmlID, property)
}
}
func (session *sessionData) startUpdateScript(htmlID string) bool {
if session.brige != nil {
return session.brige.startUpdateScript(htmlID)
if session.bridge != nil {
return session.bridge.startUpdateScript(htmlID)
}
return false
}
func (session *sessionData) finishUpdateScript(htmlID string) {
if session.brige != nil {
session.brige.finishUpdateScript(htmlID)
if session.bridge != nil {
session.bridge.finishUpdateScript(htmlID)
}
}
func (session *sessionData) cavnasStart(htmlID string) {
if session.brige != nil {
session.brige.cavnasStart(htmlID)
if session.bridge != nil {
session.bridge.cavnasStart(htmlID)
}
}
func (session *sessionData) callCanvasFunc(funcName string, args ...any) {
if session.brige != nil {
session.brige.callCanvasFunc(funcName, args...)
if session.bridge != nil {
session.bridge.callCanvasFunc(funcName, args...)
}
}
func (session *sessionData) updateCanvasProperty(property string, value any) {
if session.brige != nil {
session.brige.updateCanvasProperty(property, value)
if session.bridge != nil {
session.bridge.updateCanvasProperty(property, value)
}
}
func (session *sessionData) createCanvasVar(funcName string, args ...any) any {
if session.brige != nil {
return session.brige.createCanvasVar(funcName, args...)
if session.bridge != nil {
return session.bridge.createCanvasVar(funcName, args...)
}
return nil
}
func (session *sessionData) callCanvasVarFunc(v any, funcName string, args ...any) {
if session.brige != nil && v != nil {
session.brige.callCanvasVarFunc(v, funcName, args...)
if session.bridge != nil && v != nil {
session.bridge.callCanvasVarFunc(v, funcName, args...)
}
}
func (session *sessionData) callCanvasImageFunc(url string, property string, funcName string, args ...any) {
if session.brige != nil {
session.brige.callCanvasImageFunc(url, property, funcName, args...)
if session.bridge != nil {
session.bridge.callCanvasImageFunc(url, property, funcName, args...)
}
}
func (session *sessionData) cavnasFinish() {
if session.brige != nil {
session.brige.cavnasFinish()
if session.bridge != nil {
session.bridge.cavnasFinish()
}
}
func (session *sessionData) runScript(script string) {
if session.brige != nil {
session.brige.writeMessage(script)
if session.bridge != nil {
session.bridge.writeMessage(script)
} else {
ErrorLog("No connection")
}
}
func (session *sessionData) canvasTextMetrics(htmlID, font, text string) TextMetrics {
if session.brige != nil {
return session.brige.canvasTextMetrics(htmlID, font, text)
if session.bridge != nil {
return session.bridge.canvasTextMetrics(htmlID, font, text)
}
ErrorLog("No connection")
@ -470,8 +470,8 @@ func (session *sessionData) canvasTextMetrics(htmlID, font, text string) TextMet
}
func (session *sessionData) htmlPropertyValue(htmlID, name string) string {
if session.brige != nil {
return session.brige.htmlPropertyValue(htmlID, name)
if session.bridge != nil {
return session.bridge.htmlPropertyValue(htmlID, name)
}
ErrorLog("No connection")
@ -479,7 +479,7 @@ func (session *sessionData) htmlPropertyValue(htmlID, name string) string {
}
func (session *sessionData) handleAnswer(data DataObject) {
session.brige.answerReceived(data)
session.bridge.answerReceived(data)
}
func (session *sessionData) handleRootSize(data DataObject) {
@ -620,7 +620,7 @@ func (session *sessionData) SetTitleColor(color Color) {
}
func (session *sessionData) RemoteAddr() string {
return session.brige.remoteAddr()
return session.bridge.remoteAddr()
}
func (session *sessionData) OpenURL(urlStr string) {

View File

@ -325,7 +325,7 @@ func (session *sessionData) SetLanguage(lang string) {
if lang != session.language {
session.language = lang
if session.rootView != nil && session.brige != nil {
if session.rootView != nil && session.bridge != nil {
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
@ -334,7 +334,7 @@ func (session *sessionData) SetLanguage(lang string) {
//buffer.WriteString("';\nscanElementsSize();")
//session.runScript(buffer.String())
session.brige.updateInnerHTML("ruiRootView", buffer.String())
session.bridge.updateInnerHTML("ruiRootView", buffer.String())
}
}
}

177
wasmBridge.go Normal file
View File

@ -0,0 +1,177 @@
//go:build wasm
package rui
import (
"fmt"
"strconv"
"sync"
"syscall/js"
)
type wasmBridge struct {
queue chan string
answer map[int]chan DataObject
answerID int
answerMutex sync.Mutex
closed bool
canvas js.Value
}
func createWasmBridge() webBridge {
bridge := new(wasmBridge)
bridge.queue = make(chan string, 1000)
bridge.answerID = 1
bridge.answer = make(map[int]chan DataObject)
bridge.closed = false
return bridge
}
func (bridge *wasmBridge) startUpdateScript(htmlID string) bool {
return false
}
func (bridge *wasmBridge) finishUpdateScript(htmlID string) {
}
func (bridge *wasmBridge) callFunc(funcName string, args ...any) bool {
if ProtocolInDebugLog {
text := funcName + "("
for i, arg := range args {
if i > 0 {
text += fmt.Sprintf(", `%v`", arg)
} else {
text += fmt.Sprintf("`%v`", arg)
}
}
DebugLog(text + ")")
}
js.Global().Call(funcName, args...)
return true
}
func (bridge *wasmBridge) updateInnerHTML(htmlID, html string) {
bridge.callFunc("updateInnerHTML", htmlID, html)
}
func (bridge *wasmBridge) appendToInnerHTML(htmlID, html string) {
bridge.callFunc("appendToInnerHTML", htmlID, html)
}
func (bridge *wasmBridge) updateCSSProperty(htmlID, property, value string) {
bridge.callFunc("updateCSSProperty", htmlID, property, value)
}
func (bridge *wasmBridge) updateProperty(htmlID, property string, value any) {
bridge.callFunc("updateProperty", htmlID, property, value)
}
func (bridge *wasmBridge) removeProperty(htmlID, property string) {
bridge.callFunc("removeProperty", htmlID, property)
}
func (bridge *wasmBridge) close() {
}
func (bridge *wasmBridge) readMessage() (string, bool) {
return "", false
}
func (bridge *wasmBridge) writeMessage(script string) bool {
if ProtocolInDebugLog {
DebugLog("Run script:")
DebugLog(script)
}
window := js.Global().Get("window")
window.Call("execScript", script)
return true
}
func (bridge *wasmBridge) cavnasStart(htmlID string) {
bridge.canvas = js.Global().Call("getCanvasContext", htmlID)
}
func (bridge *wasmBridge) callCanvasFunc(funcName string, args ...any) {
if !bridge.canvas.IsNull() {
for i, arg := range args {
if array, ok := arg.([]float64); ok {
arr := make([]any, len(array))
for k, x := range array {
arr[k] = x
}
args[i] = js.ValueOf(array)
}
}
bridge.canvas.Call(funcName, args...)
}
}
func (bridge *wasmBridge) callCanvasVarFunc(v any, funcName string, args ...any) {
if jsVar, ok := v.(js.Value); ok && !jsVar.IsNull() {
jsVar.Call(funcName, args...)
}
}
func (bridge *wasmBridge) callCanvasImageFunc(url string, property string, funcName string, args ...any) {
image := js.Global().Get("images").Call("get", url)
if !image.IsUndefined() && !image.IsNull() {
result := image.Call(funcName, args...)
if property != "" {
bridge.canvas.Set(property, result)
}
}
}
func (bridge *wasmBridge) createCanvasVar(funcName string, args ...any) any {
if bridge.canvas.IsNull() {
return bridge.canvas
}
return bridge.canvas.Call(funcName, args...)
}
func (bridge *wasmBridge) updateCanvasProperty(property string, value any) {
if !bridge.canvas.IsNull() {
bridge.canvas.Set(property, value)
}
}
func (bridge *wasmBridge) cavnasFinish() {
}
func (bridge *wasmBridge) canvasTextMetrics(htmlID, font, text string) TextMetrics {
result := js.Global().Call("canvasTextMetrics", 0, htmlID, font, text).String()
DebugLog(result)
// TODO
return TextMetrics{}
}
func (bridge *wasmBridge) htmlPropertyValue(htmlID, name string) string {
return ""
}
func (bridge *wasmBridge) answerReceived(answer DataObject) {
if text, ok := answer.PropertyValue("answerID"); ok {
if id, err := strconv.Atoi(text); err == nil {
if chanel, ok := bridge.answer[id]; ok {
chanel <- answer
delete(bridge.answer, id)
} else {
ErrorLog("Bad answerID = " + text + " (chan not found)")
}
} else {
ErrorLog("Invalid answerID = " + text)
}
} else {
ErrorLog("answerID not found")
}
}
func (bridge *wasmBridge) remoteAddr() string {
return "localhost"
}

View File

@ -1,144 +0,0 @@
//go:build wasm
package rui
import (
"fmt"
"strconv"
"sync"
"syscall/js"
)
type wasmBrige struct {
queue chan string
answer map[int]chan DataObject
answerID int
answerMutex sync.Mutex
closed bool
}
func createWasmBrige() webBrige {
brige := new(wasmBrige)
brige.queue = make(chan string, 1000)
brige.answerID = 1
brige.answer = make(map[int]chan DataObject)
brige.closed = false
return brige
}
func (brige *wasmBrige) startUpdateScript(htmlID string) bool {
return false
}
func (brige *wasmBrige) finishUpdateScript(htmlID string) {
}
func (brige *wasmBrige) callFunc(funcName string, args ...any) bool {
if ProtocolInDebugLog {
text := funcName + "("
for i, arg := range args {
if i > 0 {
text += fmt.Sprintf(", `%v`", arg)
} else {
text += fmt.Sprintf("`%v`", arg)
}
}
DebugLog(text + ")")
}
js.Global().Call(funcName, args...)
return true
}
func (brige *wasmBrige) updateInnerHTML(htmlID, html string) {
brige.callFunc("updateInnerHTML", htmlID, html)
}
func (brige *wasmBrige) appendToInnerHTML(htmlID, html string) {
brige.callFunc("appendToInnerHTML", htmlID, html)
}
func (brige *wasmBrige) updateCSSProperty(htmlID, property, value string) {
brige.callFunc("updateCSSProperty", htmlID, property, value)
}
func (brige *wasmBrige) updateProperty(htmlID, property string, value any) {
brige.callFunc("updateProperty", htmlID, property, value)
}
func (brige *wasmBrige) removeProperty(htmlID, property string) {
brige.callFunc("removeProperty", htmlID, property)
}
func (brige *wasmBrige) close() {
}
func (brige *wasmBrige) readMessage() (string, bool) {
return "", false
}
func (brige *wasmBrige) writeMessage(script string) bool {
if ProtocolInDebugLog {
DebugLog("Run script:")
DebugLog(script)
}
window := js.Global().Get("window")
window.Call("execScript", script)
return true
}
/*
func (brige *wasmBrige) runGetterScript(script string) DataObject {
brige.answerMutex.Lock()
answerID := brige.answerID
brige.answerID++
brige.answerMutex.Unlock()
answer := make(chan DataObject)
brige.answer[answerID] = answer
errorText := ""
js.Global().Set("answerID", strconv.Itoa(answerID))
window := js.Global().Get("window")
window.Call("execScript", script)
result := NewDataObject("error")
result.SetPropertyValue("text", errorText)
delete(brige.answer, answerID)
return result
}
*/
func (brige *wasmBrige) canvasTextMetrics(htmlID, font, text string) TextMetrics {
// TODO
return TextMetrics{}
}
func (brige *wasmBrige) htmlPropertyValue(htmlID, name string) string {
return ""
}
func (brige *wasmBrige) answerReceived(answer DataObject) {
if text, ok := answer.PropertyValue("answerID"); ok {
if id, err := strconv.Atoi(text); err == nil {
if chanel, ok := brige.answer[id]; ok {
chanel <- answer
delete(brige.answer, id)
} else {
ErrorLog("Bad answerID = " + text + " (chan not found)")
}
} else {
ErrorLog("Invalid answerID = " + text)
}
} else {
ErrorLog("answerID not found")
}
}
func (brige *wasmBrige) remoteAddr() string {
return "localhost"
}

418
webBridge.go Normal file
View File

@ -0,0 +1,418 @@
//go:build !wasm
package rui
import (
"fmt"
"net/http"
"strconv"
"strings"
"sync"
"github.com/gorilla/websocket"
)
type wsBridge struct {
conn *websocket.Conn
answer map[int]chan DataObject
answerID int
answerMutex sync.Mutex
closed bool
buffer strings.Builder
canvasBuffer strings.Builder
canvasVarNumber int
updateScripts map[string]*strings.Builder
}
type canvasVar struct {
name string
}
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 8096,
}
func CreateSocketBridge(w http.ResponseWriter, req *http.Request) webBridge {
conn, err := upgrader.Upgrade(w, req, nil)
if err != nil {
ErrorLog(err.Error())
return nil
}
bridge := new(wsBridge)
bridge.answerID = 1
bridge.answer = make(map[int]chan DataObject)
bridge.conn = conn
bridge.closed = false
bridge.updateScripts = map[string]*strings.Builder{}
return bridge
}
func (bridge *wsBridge) close() {
bridge.closed = true
bridge.conn.Close()
}
func (bridge *wsBridge) startUpdateScript(htmlID string) bool {
if _, ok := bridge.updateScripts[htmlID]; ok {
return false
}
buffer := allocStringBuilder()
bridge.updateScripts[htmlID] = buffer
buffer.WriteString("var element = document.getElementById('")
buffer.WriteString(htmlID)
buffer.WriteString("');\nif (element) {\n")
return true
}
func (bridge *wsBridge) finishUpdateScript(htmlID string) {
if buffer, ok := bridge.updateScripts[htmlID]; ok {
buffer.WriteString("scanElementsSize();\n}\n")
bridge.writeMessage(buffer.String())
freeStringBuilder(buffer)
delete(bridge.updateScripts, htmlID)
}
}
func (bridge *wsBridge) argToString(arg any) (string, bool) {
switch arg := arg.(type) {
case string:
arg = strings.ReplaceAll(arg, "\\", `\\`)
arg = strings.ReplaceAll(arg, "'", `\'`)
arg = strings.ReplaceAll(arg, "\n", `\n`)
arg = strings.ReplaceAll(arg, "\r", `\r`)
arg = strings.ReplaceAll(arg, "\t", `\t`)
arg = strings.ReplaceAll(arg, "\b", `\b`)
arg = strings.ReplaceAll(arg, "\f", `\f`)
arg = strings.ReplaceAll(arg, "\v", `\v`)
return `'` + arg + `'`, true
case rune:
switch arg {
case '\t':
return `'\t'`, true
case '\r':
return `'\r'`, true
case '\n':
return `'\n'`, true
case '\b':
return `'\b'`, true
case '\f':
return `'\f'`, true
case '\v':
return `'\v'`, true
case '\'':
return `'\''`, true
case '\\':
return `'\\'`, true
}
if arg < ' ' {
return fmt.Sprintf(`'\x%02d'`, int(arg)), true
}
return `'` + string(arg) + `'`, true
case bool:
if arg {
return "true", true
} else {
return "false", true
}
case float32:
return fmt.Sprintf("%g", float64(arg)), true
case float64:
return fmt.Sprintf("%g", arg), true
case []float64:
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
lead := '['
for _, val := range arg {
buffer.WriteRune(lead)
lead = ','
buffer.WriteString(fmt.Sprintf("%g", val))
}
buffer.WriteRune(']')
return buffer.String(), true
case canvasVar:
return arg.name, true
default:
if n, ok := isInt(arg); ok {
return fmt.Sprintf("%d", n), true
}
}
ErrorLog("Unsupported agument type")
return "", false
}
func (bridge *wsBridge) callFunc(funcName string, args ...any) bool {
bridge.buffer.Reset()
bridge.buffer.WriteString(funcName)
bridge.buffer.WriteRune('(')
for i, arg := range args {
argText, ok := bridge.argToString(arg)
if !ok {
return false
}
if i > 0 {
bridge.buffer.WriteString(", ")
}
bridge.buffer.WriteString(argText)
}
bridge.buffer.WriteString(");")
funcText := bridge.buffer.String()
if ProtocolInDebugLog {
DebugLog("Run func: " + funcText)
}
if err := bridge.conn.WriteMessage(websocket.TextMessage, []byte(funcText)); err != nil {
ErrorLog(err.Error())
return false
}
return true
}
func (bridge *wsBridge) updateInnerHTML(htmlID, html string) {
bridge.callFunc("updateInnerHTML", htmlID, html)
}
func (bridge *wsBridge) appendToInnerHTML(htmlID, html string) {
bridge.callFunc("appendToInnerHTML", htmlID, html)
}
func (bridge *wsBridge) updateCSSProperty(htmlID, property, value string) {
if buffer, ok := bridge.updateScripts[htmlID]; ok {
buffer.WriteString(`element.style['`)
buffer.WriteString(property)
buffer.WriteString(`'] = '`)
buffer.WriteString(value)
buffer.WriteString("';\n")
} else {
bridge.callFunc("updateCSSProperty", htmlID, property, value)
}
}
func (bridge *wsBridge) updateProperty(htmlID, property string, value any) {
if buffer, ok := bridge.updateScripts[htmlID]; ok {
if val, ok := bridge.argToString(value); ok {
buffer.WriteString(`element.setAttribute('`)
buffer.WriteString(property)
buffer.WriteString(`', `)
buffer.WriteString(val)
buffer.WriteString(");\n")
}
} else {
bridge.callFunc("updateProperty", htmlID, property, value)
}
}
func (bridge *wsBridge) removeProperty(htmlID, property string) {
if buffer, ok := bridge.updateScripts[htmlID]; ok {
buffer.WriteString(`if (element.hasAttribute('`)
buffer.WriteString(property)
buffer.WriteString(`')) { element.removeAttribute('`)
buffer.WriteString(property)
buffer.WriteString("');}\n")
} else {
bridge.callFunc("removeProperty", htmlID, property)
}
}
func (bridge *wsBridge) cavnasStart(htmlID string) {
bridge.canvasBuffer.Reset()
bridge.canvasBuffer.WriteString(`const ctx = getCanvasContext('`)
bridge.canvasBuffer.WriteString(htmlID)
bridge.canvasBuffer.WriteString(`');`)
}
func (bridge *wsBridge) callCanvasFunc(funcName string, args ...any) {
bridge.canvasBuffer.WriteString("\nctx.")
bridge.canvasBuffer.WriteString(funcName)
bridge.canvasBuffer.WriteRune('(')
for i, arg := range args {
if i > 0 {
bridge.canvasBuffer.WriteString(", ")
}
argText, _ := bridge.argToString(arg)
bridge.canvasBuffer.WriteString(argText)
}
bridge.canvasBuffer.WriteString(");")
}
func (bridge *wsBridge) updateCanvasProperty(property string, value any) {
bridge.canvasBuffer.WriteString("\nctx.")
bridge.canvasBuffer.WriteString(property)
bridge.canvasBuffer.WriteString(" = ")
argText, _ := bridge.argToString(value)
bridge.canvasBuffer.WriteString(argText)
bridge.canvasBuffer.WriteString(";")
}
func (bridge *wsBridge) createCanvasVar(funcName string, args ...any) any {
bridge.canvasVarNumber++
result := canvasVar{name: fmt.Sprintf("v%d", bridge.canvasVarNumber)}
bridge.canvasBuffer.WriteString("\nvar ")
bridge.canvasBuffer.WriteString(result.name)
bridge.canvasBuffer.WriteString(" = ctx.")
bridge.canvasBuffer.WriteString(funcName)
bridge.canvasBuffer.WriteRune('(')
for i, arg := range args {
if i > 0 {
bridge.canvasBuffer.WriteString(", ")
}
argText, _ := bridge.argToString(arg)
bridge.canvasBuffer.WriteString(argText)
}
bridge.canvasBuffer.WriteString(");")
return result
}
func (bridge *wsBridge) callCanvasVarFunc(v any, funcName string, args ...any) {
varName, ok := v.(canvasVar)
if !ok {
return
}
bridge.canvasBuffer.WriteString("\n")
bridge.canvasBuffer.WriteString(varName.name)
bridge.canvasBuffer.WriteRune('.')
bridge.canvasBuffer.WriteString(funcName)
bridge.canvasBuffer.WriteRune('(')
for i, arg := range args {
if i > 0 {
bridge.canvasBuffer.WriteString(", ")
}
argText, _ := bridge.argToString(arg)
bridge.canvasBuffer.WriteString(argText)
}
bridge.canvasBuffer.WriteString(");")
}
func (bridge *wsBridge) callCanvasImageFunc(url string, property string, funcName string, args ...any) {
bridge.canvasBuffer.WriteString("\nimg = images.get('")
bridge.canvasBuffer.WriteString(url)
bridge.canvasBuffer.WriteString("');\nif (img) {\n")
if property != "" {
bridge.canvasBuffer.WriteString("ctx.")
bridge.canvasBuffer.WriteString(property)
bridge.canvasBuffer.WriteString(" = ")
}
bridge.canvasBuffer.WriteString("ctx.")
bridge.canvasBuffer.WriteString(funcName)
bridge.canvasBuffer.WriteString("(img")
for _, arg := range args {
bridge.canvasBuffer.WriteString(", ")
argText, _ := bridge.argToString(arg)
bridge.canvasBuffer.WriteString(argText)
}
bridge.canvasBuffer.WriteString(");\n}")
}
func (bridge *wsBridge) cavnasFinish() {
bridge.canvasBuffer.WriteString("\n")
script := bridge.canvasBuffer.String()
if ProtocolInDebugLog {
DebugLog("Run script:")
DebugLog(script)
}
if bridge.conn == nil {
ErrorLog("No connection")
} else if err := bridge.conn.WriteMessage(websocket.TextMessage, []byte(script)); err != nil {
ErrorLog(err.Error())
}
}
func (bridge *wsBridge) readMessage() (string, bool) {
_, p, err := bridge.conn.ReadMessage()
if err != nil {
if !bridge.closed {
ErrorLog(err.Error())
}
return "", false
}
return string(p), true
}
func (bridge *wsBridge) writeMessage(script string) bool {
if ProtocolInDebugLog {
DebugLog("Run script:")
DebugLog(script)
}
if bridge.conn == nil {
ErrorLog("No connection")
return false
}
if err := bridge.conn.WriteMessage(websocket.TextMessage, []byte(script)); err != nil {
ErrorLog(err.Error())
return false
}
return true
}
func (bridge *wsBridge) canvasTextMetrics(htmlID, font, text string) TextMetrics {
result := TextMetrics{}
bridge.answerMutex.Lock()
answerID := bridge.answerID
bridge.answerID++
bridge.answerMutex.Unlock()
answer := make(chan DataObject)
bridge.answer[answerID] = answer
if bridge.callFunc("canvasTextMetrics", answerID, htmlID, font, text) {
data := <-answer
result.Width = dataFloatProperty(data, "width")
}
delete(bridge.answer, answerID)
return result
}
func (bridge *wsBridge) htmlPropertyValue(htmlID, name string) string {
bridge.answerMutex.Lock()
answerID := bridge.answerID
bridge.answerID++
bridge.answerMutex.Unlock()
answer := make(chan DataObject)
bridge.answer[answerID] = answer
if bridge.callFunc("getPropertyValue", answerID, htmlID, name) {
data := <-answer
if value, ok := data.PropertyValue("value"); ok {
return value
}
}
delete(bridge.answer, answerID)
return ""
}
func (bridge *wsBridge) answerReceived(answer DataObject) {
if text, ok := answer.PropertyValue("answerID"); ok {
if id, err := strconv.Atoi(text); err == nil {
if chanel, ok := bridge.answer[id]; ok {
chanel <- answer
delete(bridge.answer, id)
} else {
ErrorLog("Bad answerID = " + text + " (chan not found)")
}
} else {
ErrorLog("Invalid answerID = " + text)
}
} else {
ErrorLog("answerID not found")
}
}
func (bridge *wsBridge) remoteAddr() string {
return bridge.conn.RemoteAddr().String()
}

View File

@ -1,418 +0,0 @@
//go:build !wasm
package rui
import (
"fmt"
"net/http"
"strconv"
"strings"
"sync"
"github.com/gorilla/websocket"
)
type wsBrige struct {
conn *websocket.Conn
answer map[int]chan DataObject
answerID int
answerMutex sync.Mutex
closed bool
buffer strings.Builder
canvasBuffer strings.Builder
canvasVarNumber int
updateScripts map[string]*strings.Builder
}
type canvasVar struct {
name string
}
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 8096,
}
func CreateSocketBrige(w http.ResponseWriter, req *http.Request) webBrige {
conn, err := upgrader.Upgrade(w, req, nil)
if err != nil {
ErrorLog(err.Error())
return nil
}
brige := new(wsBrige)
brige.answerID = 1
brige.answer = make(map[int]chan DataObject)
brige.conn = conn
brige.closed = false
brige.updateScripts = map[string]*strings.Builder{}
return brige
}
func (brige *wsBrige) close() {
brige.closed = true
brige.conn.Close()
}
func (brige *wsBrige) startUpdateScript(htmlID string) bool {
if _, ok := brige.updateScripts[htmlID]; ok {
return false
}
buffer := allocStringBuilder()
brige.updateScripts[htmlID] = buffer
buffer.WriteString("var element = document.getElementById('")
buffer.WriteString(htmlID)
buffer.WriteString("');\nif (element) {\n")
return true
}
func (brige *wsBrige) finishUpdateScript(htmlID string) {
if buffer, ok := brige.updateScripts[htmlID]; ok {
buffer.WriteString("scanElementsSize();\n}\n")
brige.writeMessage(buffer.String())
freeStringBuilder(buffer)
delete(brige.updateScripts, htmlID)
}
}
func (brige *wsBrige) argToString(arg any) (string, bool) {
switch arg := arg.(type) {
case string:
arg = strings.ReplaceAll(arg, "\\", `\\`)
arg = strings.ReplaceAll(arg, "'", `\'`)
arg = strings.ReplaceAll(arg, "\n", `\n`)
arg = strings.ReplaceAll(arg, "\r", `\r`)
arg = strings.ReplaceAll(arg, "\t", `\t`)
arg = strings.ReplaceAll(arg, "\b", `\b`)
arg = strings.ReplaceAll(arg, "\f", `\f`)
arg = strings.ReplaceAll(arg, "\v", `\v`)
return `'` + arg + `'`, true
case rune:
switch arg {
case '\t':
return `'\t'`, true
case '\r':
return `'\r'`, true
case '\n':
return `'\n'`, true
case '\b':
return `'\b'`, true
case '\f':
return `'\f'`, true
case '\v':
return `'\v'`, true
case '\'':
return `'\''`, true
case '\\':
return `'\\'`, true
}
if arg < ' ' {
return fmt.Sprintf(`'\x%02d'`, int(arg)), true
}
return `'` + string(arg) + `'`, true
case bool:
if arg {
return "true", true
} else {
return "false", true
}
case float32:
return fmt.Sprintf("%g", float64(arg)), true
case float64:
return fmt.Sprintf("%g", arg), true
case []float64:
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
lead := '['
for _, val := range arg {
buffer.WriteRune(lead)
lead = ','
buffer.WriteString(fmt.Sprintf("%g", val))
}
buffer.WriteRune(']')
return buffer.String(), true
case canvasVar:
return arg.name, true
default:
if n, ok := isInt(arg); ok {
return fmt.Sprintf("%d", n), true
}
}
ErrorLog("Unsupported agument type")
return "", false
}
func (brige *wsBrige) callFunc(funcName string, args ...any) bool {
brige.buffer.Reset()
brige.buffer.WriteString(funcName)
brige.buffer.WriteRune('(')
for i, arg := range args {
argText, ok := brige.argToString(arg)
if !ok {
return false
}
if i > 0 {
brige.buffer.WriteString(", ")
}
brige.buffer.WriteString(argText)
}
brige.buffer.WriteString(");")
funcText := brige.buffer.String()
if ProtocolInDebugLog {
DebugLog("Run func: " + funcText)
}
if err := brige.conn.WriteMessage(websocket.TextMessage, []byte(funcText)); err != nil {
ErrorLog(err.Error())
return false
}
return true
}
func (brige *wsBrige) updateInnerHTML(htmlID, html string) {
brige.callFunc("updateInnerHTML", htmlID, html)
}
func (brige *wsBrige) appendToInnerHTML(htmlID, html string) {
brige.callFunc("appendToInnerHTML", htmlID, html)
}
func (brige *wsBrige) updateCSSProperty(htmlID, property, value string) {
if buffer, ok := brige.updateScripts[htmlID]; ok {
buffer.WriteString(`element.style['`)
buffer.WriteString(property)
buffer.WriteString(`'] = '`)
buffer.WriteString(value)
buffer.WriteString("';\n")
} else {
brige.callFunc("updateCSSProperty", htmlID, property, value)
}
}
func (brige *wsBrige) updateProperty(htmlID, property string, value any) {
if buffer, ok := brige.updateScripts[htmlID]; ok {
if val, ok := brige.argToString(value); ok {
buffer.WriteString(`element.setAttribute('`)
buffer.WriteString(property)
buffer.WriteString(`', `)
buffer.WriteString(val)
buffer.WriteString(");\n")
}
} else {
brige.callFunc("updateProperty", htmlID, property, value)
}
}
func (brige *wsBrige) removeProperty(htmlID, property string) {
if buffer, ok := brige.updateScripts[htmlID]; ok {
buffer.WriteString(`if (element.hasAttribute('`)
buffer.WriteString(property)
buffer.WriteString(`')) { element.removeAttribute('`)
buffer.WriteString(property)
buffer.WriteString("');}\n")
} else {
brige.callFunc("removeProperty", htmlID, property)
}
}
func (brige *wsBrige) cavnasStart(htmlID string) {
brige.canvasBuffer.Reset()
brige.canvasBuffer.WriteString(`const ctx = getCanvasContext('`)
brige.canvasBuffer.WriteString(htmlID)
brige.canvasBuffer.WriteString(`');`)
}
func (brige *wsBrige) callCanvasFunc(funcName string, args ...any) {
brige.canvasBuffer.WriteString("\nctx.")
brige.canvasBuffer.WriteString(funcName)
brige.canvasBuffer.WriteRune('(')
for i, arg := range args {
if i > 0 {
brige.canvasBuffer.WriteString(", ")
}
argText, _ := brige.argToString(arg)
brige.canvasBuffer.WriteString(argText)
}
brige.canvasBuffer.WriteString(");")
}
func (brige *wsBrige) updateCanvasProperty(property string, value any) {
brige.canvasBuffer.WriteString("\nctx.")
brige.canvasBuffer.WriteString(property)
brige.canvasBuffer.WriteString(" = ")
argText, _ := brige.argToString(value)
brige.canvasBuffer.WriteString(argText)
brige.canvasBuffer.WriteString(";")
}
func (brige *wsBrige) createCanvasVar(funcName string, args ...any) any {
brige.canvasVarNumber++
result := canvasVar{name: fmt.Sprintf("v%d", brige.canvasVarNumber)}
brige.canvasBuffer.WriteString("\nvar ")
brige.canvasBuffer.WriteString(result.name)
brige.canvasBuffer.WriteString(" = ctx.")
brige.canvasBuffer.WriteString(funcName)
brige.canvasBuffer.WriteRune('(')
for i, arg := range args {
if i > 0 {
brige.canvasBuffer.WriteString(", ")
}
argText, _ := brige.argToString(arg)
brige.canvasBuffer.WriteString(argText)
}
brige.canvasBuffer.WriteString(");")
return result
}
func (brige *wsBrige) callCanvasVarFunc(v any, funcName string, args ...any) {
varName, ok := v.(canvasVar)
if !ok {
return
}
brige.canvasBuffer.WriteString("\n")
brige.canvasBuffer.WriteString(varName.name)
brige.canvasBuffer.WriteRune('.')
brige.canvasBuffer.WriteString(funcName)
brige.canvasBuffer.WriteRune('(')
for i, arg := range args {
if i > 0 {
brige.canvasBuffer.WriteString(", ")
}
argText, _ := brige.argToString(arg)
brige.canvasBuffer.WriteString(argText)
}
brige.canvasBuffer.WriteString(");")
}
func (brige *wsBrige) callCanvasImageFunc(url string, property string, funcName string, args ...any) {
brige.canvasBuffer.WriteString("\nimg = images.get('")
brige.canvasBuffer.WriteString(url)
brige.canvasBuffer.WriteString("');\nif (img) {\n")
if property != "" {
brige.canvasBuffer.WriteString("ctx.")
brige.canvasBuffer.WriteString(property)
brige.canvasBuffer.WriteString(" = ")
}
brige.canvasBuffer.WriteString("ctx.")
brige.canvasBuffer.WriteString(funcName)
brige.canvasBuffer.WriteString("(img")
for _, arg := range args {
brige.canvasBuffer.WriteString(", ")
argText, _ := brige.argToString(arg)
brige.canvasBuffer.WriteString(argText)
}
brige.canvasBuffer.WriteString(");\n}")
}
func (brige *wsBrige) cavnasFinish() {
brige.canvasBuffer.WriteString("\n")
script := brige.canvasBuffer.String()
if ProtocolInDebugLog {
DebugLog("Run script:")
DebugLog(script)
}
if brige.conn == nil {
ErrorLog("No connection")
} else if err := brige.conn.WriteMessage(websocket.TextMessage, []byte(script)); err != nil {
ErrorLog(err.Error())
}
}
func (brige *wsBrige) readMessage() (string, bool) {
_, p, err := brige.conn.ReadMessage()
if err != nil {
if !brige.closed {
ErrorLog(err.Error())
}
return "", false
}
return string(p), true
}
func (brige *wsBrige) writeMessage(script string) bool {
if ProtocolInDebugLog {
DebugLog("Run script:")
DebugLog(script)
}
if brige.conn == nil {
ErrorLog("No connection")
return false
}
if err := brige.conn.WriteMessage(websocket.TextMessage, []byte(script)); err != nil {
ErrorLog(err.Error())
return false
}
return true
}
func (brige *wsBrige) canvasTextMetrics(htmlID, font, text string) TextMetrics {
result := TextMetrics{}
brige.answerMutex.Lock()
answerID := brige.answerID
brige.answerID++
brige.answerMutex.Unlock()
answer := make(chan DataObject)
brige.answer[answerID] = answer
if brige.callFunc("canvasTextMetrics", answerID, htmlID, font, text) {
data := <-answer
result.Width = dataFloatProperty(data, "width")
}
delete(brige.answer, answerID)
return result
}
func (brige *wsBrige) htmlPropertyValue(htmlID, name string) string {
brige.answerMutex.Lock()
answerID := brige.answerID
brige.answerID++
brige.answerMutex.Unlock()
answer := make(chan DataObject)
brige.answer[answerID] = answer
if brige.callFunc("getPropertyValue", answerID, htmlID, name) {
data := <-answer
if value, ok := data.PropertyValue("value"); ok {
return value
}
}
delete(brige.answer, answerID)
return ""
}
func (brige *wsBrige) answerReceived(answer DataObject) {
if text, ok := answer.PropertyValue("answerID"); ok {
if id, err := strconv.Atoi(text); err == nil {
if chanel, ok := brige.answer[id]; ok {
chanel <- answer
delete(brige.answer, id)
} else {
ErrorLog("Bad answerID = " + text + " (chan not found)")
}
} else {
ErrorLog("Invalid answerID = " + text)
}
} else {
ErrorLog("answerID not found")
}
}
func (brige *wsBrige) remoteAddr() string {
return brige.conn.RemoteAddr().String()
}