mirror of
https://github.com/diamondburned/cchat-gtk.git
synced 2024-11-01 12:04:15 +00:00
109 lines
2.7 KiB
Go
109 lines
2.7 KiB
Go
|
package rich
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"log"
|
||
|
"reflect"
|
||
|
"time"
|
||
|
|
||
|
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||
|
)
|
||
|
|
||
|
// Reuser is an interface for structs that inherit Reusable.
|
||
|
type Reuser interface {
|
||
|
Context() context.Context
|
||
|
Acquire() int64
|
||
|
Validate(int64) bool
|
||
|
SwapResource(v interface{}, cancel func())
|
||
|
}
|
||
|
|
||
|
type AsyncUser = func(context.Context) (interface{}, func(), error)
|
||
|
|
||
|
// AsyncUse is a handler for structs that implement the Reuser primitive. The
|
||
|
// passed in function will be called asynchronously, but swap will be called in
|
||
|
// the Gtk main thread.
|
||
|
func AsyncUse(r Reuser, fn AsyncUser) {
|
||
|
// Acquire an ID.
|
||
|
id := r.Acquire()
|
||
|
ctx := r.Context()
|
||
|
|
||
|
gts.Async(func() (func(), error) {
|
||
|
// Run the callback asynchronously.
|
||
|
v, cancel, err := fn(ctx)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return func() {
|
||
|
// Validate the ID. Cancel if it's invalid.
|
||
|
if !r.Validate(id) {
|
||
|
log.Println("Async function value dropped for reusable primitive.")
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Update the resource.
|
||
|
r.SwapResource(v, cancel)
|
||
|
}, nil
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Reusable is the synchronization primitive to provide a method for
|
||
|
// asynchronous cancellation and reusability.
|
||
|
//
|
||
|
// It works by copying the ID (time) for each asynchronous operation. The
|
||
|
// operation then completes, and the ID is then compared again before being
|
||
|
// used. It provides a cancellation abstraction around the Gtk main thread.
|
||
|
//
|
||
|
// This struct is not thread-safe, as it relies on the Gtk main thread
|
||
|
// synchronization.
|
||
|
type Reusable struct {
|
||
|
time int64 // creation time, used as ID
|
||
|
ctx context.Context
|
||
|
cancel func()
|
||
|
|
||
|
swapfn reflect.Value // reflect fn
|
||
|
arg1type reflect.Type
|
||
|
}
|
||
|
|
||
|
var _ Reuser = (*Reusable)(nil)
|
||
|
|
||
|
func NewReusable(swapperFn interface{}) *Reusable {
|
||
|
r := Reusable{}
|
||
|
r.swapfn = reflect.ValueOf(swapperFn)
|
||
|
r.arg1type = r.swapfn.Type().In(0)
|
||
|
r.Invalidate()
|
||
|
return &r
|
||
|
}
|
||
|
|
||
|
// Invalidate generates a new ID for the primitive, which would render
|
||
|
// asynchronously updating elements invalid.
|
||
|
func (r *Reusable) Invalidate() {
|
||
|
// Cancel the old context.
|
||
|
if r.cancel != nil {
|
||
|
r.cancel()
|
||
|
}
|
||
|
|
||
|
// Reset.
|
||
|
r.time = time.Now().UnixNano()
|
||
|
r.ctx, r.cancel = context.WithCancel(context.Background())
|
||
|
}
|
||
|
|
||
|
// Context returns the reusable's cancellable context. It never returns nil.
|
||
|
func (r *Reusable) Context() context.Context {
|
||
|
return r.ctx
|
||
|
}
|
||
|
|
||
|
// Reusable checks the acquired ID against the current one.
|
||
|
func (r *Reusable) Validate(acquired int64) (valid bool) {
|
||
|
return r.time == acquired
|
||
|
}
|
||
|
|
||
|
// Acquire lends the ID to be given to Reusable() after finishing.
|
||
|
func (r *Reusable) Acquire() int64 {
|
||
|
return r.time
|
||
|
}
|
||
|
|
||
|
func (r *Reusable) SwapResource(v interface{}, cancel func()) {
|
||
|
r.swapfn.Call([]reflect.Value{reflect.ValueOf(v)})
|
||
|
}
|