1
0
Fork 0
mirror of https://github.com/diamondburned/cchat-gtk.git synced 2024-10-31 19:44:23 +00:00
cchat-gtk/internal/ui/rich/async.go
diamondburned (Forefront) f10aa71003 UI changes; typing state working
This commit refactors the input container's UI as well as fixing some
bugs related to asynchronous fetching of images.

It also adds complete typing indicator capabilities, all without using a
single mutex!
2020-07-03 21:41:12 -07:00

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)})
}