1
0
Fork 0
mirror of https://github.com/diamondburned/arikawa.git synced 2024-10-31 20:14:21 +00:00
arikawa/session/shard/shard.go
diamondburned 17b9c73ce3
gateway: Refactor for a better concurrent API
This commit refactors the whole package gateway as well as utils/ws
(formerly utils/wsutil) and voice/voicegateway. The new refactor
utilizes a design pattern involving a concurrent loop and an arriving
event channel.

An additional change was made to the way gateway events are typed.
Before, pretty much any type will satisfy a gateway event type, since
the actual type was just interface{}. The new refactor defines a
concrete interface that events can implement:

    type Event interface {
        Op() OpCode
        EventType() EventType
    }

Using this interface, the user can easily add custom gateway events
independently of the library without relying on string maps. This adds a
lot of type safety into the library and makes type-switching on Event
types much more reasonable.

Gateway error callbacks are also almost entirely removed in favor of
custom gateway events. A catch-all can easily be added like this:

    s.AddHandler(func(err error) {
        log.Println("gateway error:, err")
    })
2021-11-25 14:46:41 -08:00

86 lines
2.5 KiB
Go

package shard
import (
"context"
"github.com/diamondburned/arikawa/v3/api"
"github.com/diamondburned/arikawa/v3/gateway"
"github.com/diamondburned/arikawa/v3/session"
"github.com/diamondburned/arikawa/v3/utils/handler"
"github.com/pkg/errors"
)
// Shard defines a shard gateway interface that the shard manager can use.
type Shard interface {
Open(context.Context) error
Close() error
}
// NewShardFunc is the constructor to create a new gateway. For examples, see
// package session and state's. The constructor must manually connect the
// Manager's Rescale method appropriately.
//
// A new Gateway must not open any background resources until OpenCtx is called;
// if the gateway has never been opened, its Close method will never be called.
// During callback, the Manager is not locked, so the callback can use Manager's
// methods without deadlocking.
type NewShardFunc func(m *Manager, id *gateway.Identifier) (Shard, error)
// NewSessionShard creates a shard constructor for a session.
// Accessing any shard and adding a handler will add a handler for all shards.
func NewSessionShard(f func(m *Manager, s *session.Session)) NewShardFunc {
return func(m *Manager, id *gateway.Identifier) (Shard, error) {
s := session.NewCustom(*id, api.NewClient(id.Token), handler.New())
f(m, s)
return s, nil
}
}
// ShardState wraps around the Gateway interface to provide additional state.
type ShardState struct {
Shard Shard
// This is a bit wasteful: 2 constant pointers are stored here, and they
// waste GC cycles. This is unavoidable, however, since the API has to take
// in a pointer to Identifier, not IdentifyData. This is to ensure rescales
// are consistent.
ID gateway.Identifier
Opened bool
}
// ShardID returns the shard state's shard ID.
func (state ShardState) ShardID() int {
return state.ID.Shard.ShardID()
}
// OpenShards opens the gateways of the given list of shard states.
func OpenShards(ctx context.Context, shards []ShardState) error {
for i, shard := range shards {
if err := shard.Shard.Open(ctx); err != nil {
CloseShards(shards)
return errors.Wrapf(err, "failed to open shard %d/%d", i, len(shards)-1)
}
// Mark as opened so we can close them.
shards[i].Opened = true
}
return nil
}
// CloseShards closes the gateways of the given list of shard states.
func CloseShards(shards []ShardState) error {
var lastError error
for i, gw := range shards {
if gw.Opened {
if err := gw.Shard.Close(); err != nil {
lastError = err
}
shards[i].Opened = false
}
}
return lastError
}