Compare commits

...

14 Commits

Author SHA1 Message Date
Lea Anthony
bbb07e17d9 v2.0.0-alpha.18 2021-01-31 21:20:40 +11:00
Lea Anthony
e6b40b55c4 Add nil guard for binding 2021-01-31 21:10:30 +11:00
Lea Anthony
7573f68df3 Add argument guard for methods 2021-01-31 15:35:33 +11:00
Lea Anthony
ceaacc7ba9 Re-added Store.Set() using deep copy 2021-01-30 21:36:59 +11:00
Lea Anthony
0e24f75753 Deep copy initial value in store 2021-01-30 21:29:36 +11:00
Lea Anthony
82b9deeee4 Unexport set 2021-01-30 20:47:10 +11:00
Lea Anthony
cfa40b797f More race condition fixes 2021-01-29 13:03:05 +11:00
Lea Anthony
5aeb68acb7 Fix for calculating new window size 2021-01-28 19:26:26 +11:00
Lea Anthony
b81101414f Support Min/Max Size in Window runtime 2021-01-28 18:48:07 +11:00
Lea Anthony
7ae89d04bb Fixed data race in store. Handle nils better 2021-01-28 07:15:29 +11:00
Lea Anthony
1c566f3802 Fixed data race in servicebus 2021-01-28 06:23:49 +11:00
Lea Anthony
c9c3c9ab90 Don't bind startup/shutdown methods 2021-01-27 21:12:17 +11:00
Lea Anthony
56394ac50e Use mutex when doing a Store resync 2021-01-27 06:03:17 +11:00
Lea Anthony
f7c2f12ab2 Added error handler for dealing with loading user js code 2021-01-26 16:01:06 +11:00
22 changed files with 1630 additions and 39 deletions

View File

@@ -1,3 +1,3 @@
package main
var version = "v2.0.0-alpha.17"
var version = "v2.0.0-alpha.18"

View File

@@ -81,12 +81,15 @@ func CreateApp(appoptions *options.App) (*App, error) {
window := ffenestri.NewApplicationWithConfig(appoptions, myLogger, menuManager)
// Create binding exemptions - Ugly hack. There must be a better way
bindingExemptions := []interface{}{appoptions.Startup, appoptions.Shutdown}
result := &App{
appType: "desktop",
window: window,
servicebus: servicebus.New(myLogger),
logger: myLogger,
bindings: binding.NewBindings(myLogger, appoptions.Bind),
bindings: binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions),
menuManager: menuManager,
startupCallback: appoptions.Startup,
shutdownCallback: appoptions.Shutdown,

View File

@@ -2,23 +2,38 @@ package binding
import (
"fmt"
"reflect"
"runtime"
"strings"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/logger"
)
type Bindings struct {
db *DB
logger logger.CustomLogger
db *DB
logger logger.CustomLogger
exemptions slicer.StringSlicer
}
// NewBindings returns a new Bindings object
func NewBindings(logger *logger.Logger, structPointersToBind []interface{}) *Bindings {
func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exemptions []interface{}) *Bindings {
result := &Bindings{
db: newDB(),
logger: logger.CustomLogger("Bindings"),
}
for _, exemption := range exemptions {
if exemptions == nil {
continue
}
name := runtime.FuncForPC(reflect.ValueOf(exemption).Pointer()).Name()
// Yuk yuk yuk! Is there a better way?
name = strings.TrimSuffix(name, "-fm")
result.exemptions.Add(name)
}
// Add the structs to bind
for _, ptr := range structPointersToBind {
err := result.Add(ptr)
@@ -33,7 +48,7 @@ func NewBindings(logger *logger.Logger, structPointersToBind []interface{}) *Bin
// Add the given struct methods to the Bindings
func (b *Bindings) Add(structPtr interface{}) error {
methods, err := getMethods(structPtr)
methods, err := b.getMethods(structPtr)
if err != nil {
return fmt.Errorf("cannot bind value to app: %s", err.Error())
}
@@ -46,7 +61,6 @@ func (b *Bindings) Add(structPtr interface{}) error {
// Add it as a regular method
b.db.AddMethod(packageName, structName, methodName, method)
}
return nil
}

View File

@@ -30,6 +30,9 @@ func (b *BoundMethod) OutputCount() int {
func (b *BoundMethod) ParseArgs(args []json.RawMessage) ([]interface{}, error) {
result := make([]interface{}, b.InputCount())
if len(args) != b.InputCount() {
return nil, fmt.Errorf("received %d arguments to method '%s', expected %d", len(args), b.Name, b.InputCount())
}
for index, arg := range args {
typ := b.Inputs[index].reflectType
inputValue := reflect.New(typ).Interface()

View File

@@ -23,7 +23,7 @@ func isStruct(value interface{}) bool {
return reflect.ValueOf(value).Kind() == reflect.Struct
}
func getMethods(value interface{}) ([]*BoundMethod, error) {
func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
// Create result placeholder
var result []*BoundMethod
@@ -56,6 +56,11 @@ func getMethods(value interface{}) ([]*BoundMethod, error) {
fullMethodName := baseName + "." + methodName
method := structValue.MethodByName(methodName)
methodReflectName := runtime.FuncForPC(methodDef.Func.Pointer()).Name()
if b.exemptions.Contains(methodReflectName) {
continue
}
// Create new method
boundMethod := &BoundMethod{
Name: fullMethodName,

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Joel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,125 @@
// deepcopy makes deep copies of things. A standard copy will copy the
// pointers: deep copy copies the values pointed to. Unexported field
// values are not copied.
//
// Copyright (c)2014-2016, Joel Scoble (github.com/mohae), all rights reserved.
// License: MIT, for more details check the included LICENSE file.
package deepcopy
import (
"reflect"
"time"
)
// Interface for delegating copy process to type
type Interface interface {
DeepCopy() interface{}
}
// Iface is an alias to Copy; this exists for backwards compatibility reasons.
func Iface(iface interface{}) interface{} {
return Copy(iface)
}
// Copy creates a deep copy of whatever is passed to it and returns the copy
// in an interface{}. The returned value will need to be asserted to the
// correct type.
func Copy(src interface{}) interface{} {
if src == nil {
return nil
}
// Make the interface a reflect.Value
original := reflect.ValueOf(src)
// Make a copy of the same type as the original.
cpy := reflect.New(original.Type()).Elem()
// Recursively copy the original.
copyRecursive(original, cpy)
// Return the copy as an interface.
return cpy.Interface()
}
// copyRecursive does the actual copying of the interface. It currently has
// limited support for what it can handle. Add as needed.
func copyRecursive(original, cpy reflect.Value) {
// check for implement deepcopy.Interface
if original.CanInterface() {
if copier, ok := original.Interface().(Interface); ok {
cpy.Set(reflect.ValueOf(copier.DeepCopy()))
return
}
}
// handle according to original's Kind
switch original.Kind() {
case reflect.Ptr:
// Get the actual value being pointed to.
originalValue := original.Elem()
// if it isn't valid, return.
if !originalValue.IsValid() {
return
}
cpy.Set(reflect.New(originalValue.Type()))
copyRecursive(originalValue, cpy.Elem())
case reflect.Interface:
// If this is a nil, don't do anything
if original.IsNil() {
return
}
// Get the value for the interface, not the pointer.
originalValue := original.Elem()
// Get the value by calling Elem().
copyValue := reflect.New(originalValue.Type()).Elem()
copyRecursive(originalValue, copyValue)
cpy.Set(copyValue)
case reflect.Struct:
t, ok := original.Interface().(time.Time)
if ok {
cpy.Set(reflect.ValueOf(t))
return
}
// Go through each field of the struct and copy it.
for i := 0; i < original.NumField(); i++ {
// The Type's StructField for a given field is checked to see if StructField.PkgPath
// is set to determine if the field is exported or not because CanSet() returns false
// for settable fields. I'm not sure why. -mohae
if original.Type().Field(i).PkgPath != "" {
continue
}
copyRecursive(original.Field(i), cpy.Field(i))
}
case reflect.Slice:
if original.IsNil() {
return
}
// Make a new slice and copy each element.
cpy.Set(reflect.MakeSlice(original.Type(), original.Len(), original.Cap()))
for i := 0; i < original.Len(); i++ {
copyRecursive(original.Index(i), cpy.Index(i))
}
case reflect.Map:
if original.IsNil() {
return
}
cpy.Set(reflect.MakeMap(original.Type()))
for _, key := range original.MapKeys() {
originalValue := original.MapIndex(key)
copyValue := reflect.New(originalValue.Type()).Elem()
copyRecursive(originalValue, copyValue)
copyKey := Copy(key.Interface())
cpy.SetMapIndex(reflect.ValueOf(copyKey), copyValue)
}
default:
cpy.Set(original)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -12,9 +12,10 @@ package ffenestri
import "C"
import (
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"strconv"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"github.com/wailsapp/wails/v2/internal/logger"
)
@@ -113,6 +114,14 @@ func (c *Client) WindowSize(width int, height int) {
C.SetSize(c.app.app, C.int(width), C.int(height))
}
func (c *Client) WindowSetMinSize(width int, height int) {
C.SetMinWindowSize(c.app.app, C.int(width), C.int(height))
}
func (c *Client) WindowSetMaxSize(width int, height int) {
C.SetMaxWindowSize(c.app.app, C.int(width), C.int(height))
}
// WindowSetColour sets the window colour
func (c *Client) WindowSetColour(colour int) {
r, g, b, a := intToColour(colour)

View File

@@ -140,6 +140,16 @@ void Debug(struct Application *app, const char *message, ... ) {
}
}
void Error(struct Application *app, const char *message, ... ) {
const char *temp = concat("LEFfenestri (C) | ", message);
va_list args;
va_start(args, message);
vsnprintf(logbuffer, MAXMESSAGE, temp, args);
app->sendMessageToBackend(&logbuffer[0]);
MEMFREE(temp);
va_end(args);
}
void Fatal(struct Application *app, const char *message, ... ) {
const char *temp = concat("LFFfenestri (C) | ", message);
va_list args;
@@ -246,7 +256,10 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
struct Application *app = (struct Application *)objc_getAssociatedObject(
self, "application");
const char *name = (const char *)msg(msg(message, s("name")), s("UTF8String"));
if( strcmp(name, "completed") == 0) {
if( strcmp(name, "error") == 0 ) {
printf("There was a Javascript error. Please open the devtools for more information.\n");
Show(app);
} else if( strcmp(name, "completed") == 0) {
// Delete handler
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("completed"));
@@ -450,6 +463,7 @@ void DestroyApplication(struct Application *app) {
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("contextMenu"));
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("windowDrag"));
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("external"));
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("error"));
// Close main window
msg(app->mainWindow, s("close"));
@@ -885,6 +899,20 @@ void setMinMaxSize(struct Application *app)
{
msg(app->mainWindow, s("setMinSize:"), CGSizeMake(app->minWidth, app->minHeight));
}
// Calculate if window needs resizing
int newWidth = app->width;
int newHeight = app->height;
if (app->maxWidth > 0 && app->width > app->maxWidth) newWidth = app->maxWidth;
if (app->minWidth > 0 && app->width < app->minWidth) newWidth = app->minWidth;
if (app->maxHeight > 0 && app->height > app->maxHeight ) newHeight = app->maxHeight;
if (app->minHeight > 0 && app->height < app->minHeight ) newHeight = app->minHeight;
// If we have any change, resize window
if ( newWidth != app->width || newHeight != app->height ) {
SetSize(app, newWidth, newHeight);
}
}
void SetMinWindowSize(struct Application *app, int minWidth, int minHeight)
@@ -1534,6 +1562,7 @@ void Run(struct Application *app, int argc, char **argv) {
id manager = msg(config, s("userContentController"));
msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("external"));
msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("completed"));
msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("error"));
app->manager = manager;
id wkwebview = msg(c("WKWebView"), s("alloc"));

File diff suppressed because one or more lines are too long

View File

@@ -2,6 +2,7 @@ package messagedispatcher
import (
"fmt"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
"github.com/wailsapp/wails/v2/internal/servicebus"
@@ -26,6 +27,8 @@ type Client interface {
WindowUnminimise()
WindowPosition(x int, y int)
WindowSize(width int, height int)
WindowSetMinSize(width int, height int)
WindowSetMaxSize(width int, height int)
WindowFullscreen()
WindowUnFullscreen()
WindowSetColour(colour int)

View File

@@ -2,11 +2,12 @@ package messagedispatcher
import (
"encoding/json"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"strconv"
"strings"
"sync"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"github.com/wailsapp/wails/v2/internal/crypto"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
@@ -349,6 +350,38 @@ func (d *Dispatcher) processWindowMessage(result *servicebus.Message) {
for _, client := range d.clients {
client.frontend.WindowSize(w, h)
}
case "minsize":
// We need 2 arguments
if len(splitTopic) != 4 {
d.logger.Error("Invalid number of parameters for 'window:minsize' : %#v", result.Data())
return
}
w, err1 := strconv.Atoi(splitTopic[2])
h, err2 := strconv.Atoi(splitTopic[3])
if err1 != nil || err2 != nil {
d.logger.Error("Invalid integer parameters for 'window:minsize' : %#v", result.Data())
return
}
// Notifh clients
for _, client := range d.clients {
client.frontend.WindowSetMinSize(w, h)
}
case "maxsize":
// We need 2 arguments
if len(splitTopic) != 4 {
d.logger.Error("Invalid number of parameters for 'window:maxsize' : %#v", result.Data())
return
}
w, err1 := strconv.Atoi(splitTopic[2])
h, err2 := strconv.Atoi(splitTopic[3])
if err1 != nil || err2 != nil {
d.logger.Error("Invalid integer parameters for 'window:maxsize' : %#v", result.Data())
return
}
// Notifh clients
for _, client := range d.clients {
client.frontend.WindowSetMaxSize(w, h)
}
default:
d.logger.Error("Unknown window command: %s", command)
}

File diff suppressed because one or more lines are too long

View File

@@ -10,6 +10,7 @@ The lightweight framework for web-like apps
/* jshint esversion: 6 */
import { SetBindings } from './bindings';
import { Init } from './main';
import {RaiseError} from '../desktop/darwin';
// Setup global error handler
window.onerror = function (msg, url, lineNo, columnNo, error) {
@@ -21,7 +22,7 @@ window.onerror = function (msg, url, lineNo, columnNo, error) {
error: JSON.stringify(error),
stack: function() { return JSON.stringify(new Error().stack); }(),
};
window.wails.Log.Error(JSON.stringify(errorMessage));
RaiseError(errorMessage);
};
// Initialise the Runtime

View File

@@ -25,6 +25,10 @@ export function SendMessage(message) {
window.webkit.messageHandlers.external.postMessage(message);
}
export function RaiseError(message) {
window.webkit.messageHandlers.error.postMessage(message);
}
export function Init() {
// Setup drag handler

View File

@@ -13,7 +13,7 @@ module.exports = {
mode: 'production',
output: {
path: path.resolve(__dirname, '..', 'assets'),
filename: 'desktop.js',
filename: 'desktop_'+platform+'.js',
library: 'Wails'
},
resolve: {

View File

@@ -3,12 +3,13 @@ package main
import (
"bytes"
"fmt"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/shell"
"io/ioutil"
"log"
"os"
"strings"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/shell"
)
func main() {
@@ -49,7 +50,7 @@ func main() {
log.Fatal(err)
}
wailsJS := fs.RelativePath("../../../internal/runtime/assets/desktop.js")
wailsJS := fs.RelativePath("../../../internal/runtime/assets/desktop_" + platform + ".js")
runtimeData, err := ioutil.ReadFile(wailsJS)
if err != nil {
log.Fatal(err)

View File

@@ -6,9 +6,12 @@ import (
"bytes"
"encoding/json"
"fmt"
golog "log"
"os"
"reflect"
"sync"
"github.com/wailsapp/wails/v2/internal/deepcopy"
)
// Options defines the optional data that may be used
@@ -64,21 +67,31 @@ func fatal(err error) {
// New creates a new store
func (p *StoreProvider) New(name string, defaultValue interface{}) *Store {
dataType := reflect.TypeOf(defaultValue)
if defaultValue == nil {
golog.Fatal("Cannot initialise a store with nil")
}
result := Store{
name: name,
runtime: p.runtime,
data: reflect.ValueOf(defaultValue),
dataType: dataType,
name: name,
runtime: p.runtime,
}
// Setup the sync listener
result.setupListener()
result.Set(defaultValue)
return &result
}
func (s *Store) lock() {
s.mux.Lock()
}
func (s *Store) unlock() {
s.mux.Unlock()
}
// OnError takes a function that will be called
// whenever an error occurs
func (s *Store) OnError(callback func(error)) {
@@ -105,7 +118,7 @@ func (s *Store) processUpdatedData(data string) error {
}
// Lock mutex for writing
s.mux.Lock()
s.lock()
// Handle nulls
if newData == nil {
@@ -116,7 +129,7 @@ func (s *Store) processUpdatedData(data string) error {
}
// Unlock mutex
s.mux.Unlock()
s.unlock()
return nil
}
@@ -153,17 +166,27 @@ func (s *Store) setupListener() {
func (s *Store) resync() {
// Stringify data
newdata, err := json.Marshal(s.data.Interface())
if err != nil {
if s.errorHandler != nil {
s.errorHandler(err)
return
// Lock
s.lock()
defer s.unlock()
var result string
if s.data.IsValid() {
rawdata, err := json.Marshal(s.data.Interface())
if err != nil {
if s.errorHandler != nil {
s.errorHandler(err)
return
}
}
result = string(rawdata)
} else {
result = "{}"
}
// Emit event to front end
s.runtime.Events.Emit("wails:sync:store:updatedbybackend:"+s.name, string(newdata))
s.runtime.Events.Emit("wails:sync:store:updatedbybackend:"+s.name, result)
// Notify subscribers
s.notify()
@@ -176,7 +199,9 @@ func (s *Store) notify() {
for _, callback := range s.callbacks {
// Build args
s.lock()
args := []reflect.Value{s.data}
s.unlock()
if s.notifySynchronously {
callback.Call(args)
@@ -191,16 +216,31 @@ func (s *Store) notify() {
// and notify listeners of the change
func (s *Store) Set(data interface{}) error {
inType := reflect.TypeOf(data)
if data == nil {
return fmt.Errorf("cannot set store to nil")
}
if inType != s.dataType {
return fmt.Errorf("invalid data given in Store.Set(). Expected %s, got %s", s.dataType.String(), inType.String())
s.lock()
dataCopy := deepcopy.Copy(data)
if dataCopy != nil {
inType := reflect.TypeOf(dataCopy)
if inType != s.dataType && s.data.IsValid() {
s.unlock()
return fmt.Errorf("invalid data given in Store.Set(). Expected %s, got %s", s.dataType.String(), inType.String())
}
}
if s.dataType == nil {
s.dataType = reflect.TypeOf(dataCopy)
}
// Save data
s.mux.Lock()
s.data = reflect.ValueOf(data)
s.mux.Unlock()
s.data = reflect.ValueOf(dataCopy)
s.unlock()
// Resync with subscribers
s.resync()
@@ -251,7 +291,9 @@ func (s *Store) Subscribe(callback interface{}) {
callbackFunc := reflect.ValueOf(callback)
s.lock()
s.callbacks = append(s.callbacks, callbackFunc)
s.unlock()
}
// updaterCheck ensures the given function to Update() is
@@ -301,7 +343,9 @@ func (s *Store) Update(updater interface{}) {
}
// Build args
s.lock()
args := []reflect.Value{s.data}
s.unlock()
// Make call
results := reflect.ValueOf(updater).Call(args)
@@ -312,5 +356,12 @@ func (s *Store) Update(updater interface{}) {
// Get returns the value of the data that's kept in the current state / Store
func (s *Store) Get() interface{} {
s.lock()
defer s.unlock()
if !s.data.IsValid() {
return nil
}
return s.data.Interface()
}

View File

@@ -0,0 +1,165 @@
package runtime
import (
"context"
"math/rand"
"sync"
"testing"
"time"
internallogger "github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/pkg/logger"
is2 "github.com/matryer/is"
)
func TestStoreProvider_NewWithNilDefault(t *testing.T) {
is := is2.New(t)
defaultLogger := logger.NewDefaultLogger()
testLogger := internallogger.New(defaultLogger)
//testLogger.SetLogLevel(logger.TRACE)
serviceBus := servicebus.New(testLogger)
err := serviceBus.Start()
is.NoErr(err)
defer serviceBus.Stop()
testRuntime := New(serviceBus)
storeProvider := newStore(testRuntime)
testStore := storeProvider.New("test", 0)
// You should be able to write a new value into a
// store initialised with nil
err = testStore.Set(100)
is.NoErr(err)
// You shouldn't be able to write different types to the
// store
err = testStore.Set(false)
is.True(err != nil)
}
func TestStoreProvider_NewWithScalarDefault(t *testing.T) {
is := is2.New(t)
defaultLogger := logger.NewDefaultLogger()
testLogger := internallogger.New(defaultLogger)
//testLogger.SetLogLevel(logger.TRACE)
serviceBus := servicebus.New(testLogger)
err := serviceBus.Start()
is.NoErr(err)
defer serviceBus.Stop()
testRuntime := New(serviceBus)
storeProvider := newStore(testRuntime)
testStore := storeProvider.New("test", 100)
value := testStore.Get()
is.Equal(value, 100)
testStore.resync()
value = testStore.Get()
is.Equal(value, 100)
}
func TestStoreProvider_NewWithStructDefault(t *testing.T) {
is := is2.New(t)
defaultLogger := logger.NewDefaultLogger()
testLogger := internallogger.New(defaultLogger)
//testLogger.SetLogLevel(logger.TRACE)
serviceBus := servicebus.New(testLogger)
err := serviceBus.Start()
is.NoErr(err)
defer serviceBus.Stop()
testRuntime := New(serviceBus)
storeProvider := newStore(testRuntime)
type TestValue struct {
Name string
}
testValue := &TestValue{
Name: "hi",
}
testStore := storeProvider.New("test", testValue)
err = testStore.Set(testValue)
is.NoErr(err)
testStore.resync()
value := testStore.Get()
is.Equal(value, testValue)
is.Equal(value.(*TestValue).Name, "hi")
testValue = &TestValue{
Name: "there",
}
err = testStore.Set(testValue)
is.NoErr(err)
testStore.resync()
value = testStore.Get()
is.Equal(value, testValue)
is.Equal(value.(*TestValue).Name, "there")
}
func TestStoreProvider_RapidReadWrite(t *testing.T) {
is := is2.New(t)
defaultLogger := logger.NewDefaultLogger()
testLogger := internallogger.New(defaultLogger)
//testLogger.SetLogLevel(logger.TRACE)
serviceBus := servicebus.New(testLogger)
err := serviceBus.Start()
is.NoErr(err)
defer serviceBus.Stop()
testRuntime := New(serviceBus)
storeProvider := newStore(testRuntime)
testStore := storeProvider.New("test", 1)
ctx, _ := context.WithTimeout(context.Background(), 3*time.Second)
var wg sync.WaitGroup
readers := 100
writers := 100
wg.Add(readers + writers)
// Setup readers
go func(testStore *Store, ctx context.Context) {
for readerCount := 0; readerCount < readers; readerCount++ {
go func(store *Store, ctx context.Context, id int) {
for {
select {
case <-ctx.Done():
wg.Done()
return
default:
store.Get()
}
}
}(testStore, ctx, readerCount)
}
}(testStore, ctx)
// Setup writers
go func(testStore *Store, ctx context.Context) {
for writerCount := 0; writerCount < writers; writerCount++ {
go func(store *Store, ctx context.Context, id int) {
for {
select {
case <-ctx.Done():
wg.Done()
return
default:
err := store.Set(rand.Int())
is.NoErr(err)
}
}
}(testStore, ctx, writerCount)
}
}(testStore, ctx)
wg.Wait()
}

View File

@@ -18,6 +18,8 @@ type Window interface {
Unminimise()
SetTitle(title string)
SetSize(width int, height int)
SetMinSize(width int, height int)
SetMaxSize(width int, height int)
SetPosition(x int, y int)
Fullscreen()
UnFullscreen()
@@ -85,6 +87,18 @@ func (w *window) SetSize(width int, height int) {
w.bus.Publish(message, "")
}
// SetSize sets the size of the window
func (w *window) SetMinSize(width int, height int) {
message := fmt.Sprintf("window:minsize:%d:%d", width, height)
w.bus.Publish(message, "")
}
// SetSize sets the size of the window
func (w *window) SetMaxSize(width int, height int) {
message := fmt.Sprintf("window:maxsize:%d:%d", width, height)
w.bus.Publish(message, "")
}
// SetPosition sets the position of the window
func (w *window) SetPosition(x int, y int) {
message := fmt.Sprintf("window:position:%d:%d", x, y)

View File

@@ -71,10 +71,11 @@ func (s *ServiceBus) Start() error {
}
// We run in a different thread
s.wg.Add(1)
go func() {
quit := false
s.wg.Add(1)
// Loop until we get a quit message
for !quit {