From be3a834696e5377c6c4186368a6c445aca940821 Mon Sep 17 00:00:00 2001 From: Tnze Date: Mon, 3 Jan 2022 15:57:54 +0800 Subject: [PATCH] Command parse compound --- examples/frameworkServer/main.go | 21 ++++++++++ net/packet/util.go | 11 ++++- server/chat.go | 5 +-- server/command.go | 49 ++++++++++++++++++++++ server/command/builders.go | 12 +++--- server/command/command.go | 27 ++++++------ server/command/command_test.go | 71 +++++++++----------------------- server/command/serialize.go | 49 ++++++++++++++++++++++ server/dimension.go | 10 +---- server/gameplay.go | 33 +++++++++++---- server/keepalive.go | 6 +-- server/player.go | 62 ++++++++++++++++++++++------ server/playerlist.go | 8 +--- 13 files changed, 250 insertions(+), 114 deletions(-) create mode 100644 server/command.go create mode 100644 server/command/serialize.go diff --git a/examples/frameworkServer/main.go b/examples/frameworkServer/main.go index 496f11f..6a66f5a 100644 --- a/examples/frameworkServer/main.go +++ b/examples/frameworkServer/main.go @@ -5,6 +5,7 @@ import ( _ "embed" "flag" "fmt" + "github.com/Tnze/go-mc/server/command" "image" _ "image/png" "log" @@ -37,11 +38,31 @@ func main() { log.Fatalf("Load chunks fail: %v", err) } + commands := server.NewCommandGraph() + handleFunc := func(args []command.ParsedData) error { + log.Printf("Command: args: %v", args) + return nil + } + commands.AppendLiteral(commands.Literal("me"). + AppendArgument(commands.Argument("action", command.StringParser(2)). + HandleFunc(handleFunc)). + Unhandle(), + ).AppendLiteral(commands.Literal("help"). + AppendArgument(commands.Argument("command", command.StringParser(0)). + HandleFunc(handleFunc)). + HandleFunc(handleFunc), + ).AppendLiteral(commands.Literal("list"). + AppendLiteral(commands.Literal("uuids"). + HandleFunc(handleFunc)). + HandleFunc(handleFunc), + ) + game := server.NewGame( defaultDimension, playerList, server.NewKeepAlive(), server.NewGlobalChat(), + commands, ) go game.Run(context.Background()) diff --git a/net/packet/util.go b/net/packet/util.go index f66e8ef..801b3ef 100644 --- a/net/packet/util.go +++ b/net/packet/util.go @@ -82,7 +82,16 @@ func (a Ary) ReadFrom(r io.Reader) (n int64, err error) { // // Warning: unstable API, may change in later version func Array(array interface{}) Field { - length := VarInt(reflect.ValueOf(array).Len()) + var length VarInt + + value := reflect.ValueOf(array) + for value.Kind() == reflect.Ptr { + value = value.Elem() + } + + if array != nil { + length = VarInt(value.Len()) + } return Tuple{ &length, Ary{ diff --git a/server/chat.go b/server/chat.go index 3cdd465..329448a 100644 --- a/server/chat.go +++ b/server/chat.go @@ -87,10 +87,7 @@ func (g *GlobalChat) Run(ctx context.Context) { func (g *GlobalChat) broadcast(packet Packet757) { for _, p := range g.players { - err := p.WritePacket(packet) - if err != nil { - p.PutErr(err) - } + p.WritePacket(packet) } } diff --git a/server/command.go b/server/command.go new file mode 100644 index 0000000..930b050 --- /dev/null +++ b/server/command.go @@ -0,0 +1,49 @@ +package server + +import ( + "context" + "github.com/Tnze/go-mc/data/packetid" + pk "github.com/Tnze/go-mc/net/packet" + "github.com/Tnze/go-mc/server/command" + "strings" +) + +type CommandGraph struct { + *command.Graph +} + +func NewCommandGraph() *CommandGraph { + return &CommandGraph{ + Graph: command.NewGraph(), + } +} + +func (c *CommandGraph) Init(g *Game) { + g.AddHandler(&PacketHandler{ + ID: packetid.ServerboundChat, + F: func(player *Player, packet Packet757) error { + var msg pk.String + if err := pk.Packet(packet).Scan(&msg); err != nil { + return err + } + if cmd := string(msg); strings.HasPrefix(cmd, "/") { + cmderr := c.Graph.Run(strings.TrimPrefix(cmd, "/")) + if cmderr != nil { + // TODO: tell player that their command has error + } + } + return nil + }, + }) +} + +func (c *CommandGraph) Run(ctx context.Context) {} + +func (c *CommandGraph) AddPlayer(p *Player) { + p.WritePacket(Packet757(pk.Marshal( + packetid.ClientboundCommands, + c.Graph, + ))) +} + +func (c *CommandGraph) RemovePlayer(p *Player) {} diff --git a/server/command/builders.go b/server/command/builders.go index 9d859fb..5e9be29 100644 --- a/server/command/builders.go +++ b/server/command/builders.go @@ -6,22 +6,22 @@ func (g *Graph) AppendLiteral(child *Literal) *Graph { } func (g *Graph) Literal(str string) LiteralBuilder { - index := len(g.nodes) + index := int32(len(g.nodes)) g.nodes = append(g.nodes, Node{ g: g, index: index, - flags: LiteralNode, + kind: LiteralNode, Name: str, }) return LiteralBuilder{g: g, current: index} } func (g *Graph) Argument(name string, parser Parser) ArgumentBuilder { - index := len(g.nodes) + index := int32(len(g.nodes)) g.nodes = append(g.nodes, Node{ g: g, index: index, - flags: ArgumentNode, + kind: ArgumentNode, Name: name, Parser: parser, }) @@ -30,7 +30,7 @@ func (g *Graph) Argument(name string, parser Parser) ArgumentBuilder { type LiteralBuilder struct { g *Graph - current int + current int32 } func (n LiteralBuilder) AppendLiteral(node *Literal) LiteralBuilderWithLiteral { @@ -57,7 +57,7 @@ func (n LiteralBuilder) Unhandle() *Literal { type ArgumentBuilder struct { g *Graph - current int + current int32 } func (n ArgumentBuilder) AppendLiteral(node *Literal) ArgumentBuilderWithLiteral { diff --git a/server/command/command.go b/server/command/command.go index ff7ba90..bed2756 100644 --- a/server/command/command.go +++ b/server/command/command.go @@ -18,8 +18,8 @@ type Graph struct { func NewGraph() *Graph { g := new(Graph) root := Node{ - g: g, - flags: RootNode, + g: g, + kind: RootNode, } g.nodes = append(g.nodes, root) return g @@ -43,7 +43,7 @@ func (g *Graph) Run(cmd string) error { return nil } if next == 0 { - panic("root node can't be child") + return errors.New("command contains extra text: " + left) } cmd = left @@ -56,19 +56,20 @@ type ParsedData interface{} type HandlerFunc func(args []ParsedData) error type Node struct { - g *Graph - index int - flags byte - Name string - Children []int - Parser Parser - Run HandlerFunc + g *Graph + index int32 + kind byte + Name string + Children []int32 + SuggestionsType string + Parser Parser + Run HandlerFunc } type Literal Node type Argument Node -func (n *Node) parse(cmd string) (left string, value ParsedData, next int, err error) { - switch n.flags & 0x03 { +func (n *Node) parse(cmd string) (left string, value ParsedData, next int32, err error) { + switch n.kind & 0x03 { case RootNode: left = cmd value = nil @@ -90,7 +91,7 @@ func (n *Node) parse(cmd string) (left string, value ParsedData, next int, err e // find next if len(n.Children) > 0 { // look up the first child's type - switch n.g.nodes[n.Children[0]].flags & 0x03 { + switch n.g.nodes[n.Children[0]].kind & 0x03 { case RootNode: panic("root node can't be child") default: diff --git a/server/command/command_test.go b/server/command/command_test.go index b411048..40445eb 100644 --- a/server/command/command_test.go +++ b/server/command/command_test.go @@ -1,63 +1,32 @@ package command -import "testing" +import ( + "log" + "testing" +) func TestRoot_Run(t *testing.T) { + handleFunc := func(args []ParsedData) error { + log.Printf("Command: args: %v", args) + return nil + } g := NewGraph() - g.AppendLiteral(g.Literal("whitelist"). - AppendLiteral(g.Literal("add"). - AppendArgument(g.Argument("targets", StringParser(0)). - HandleFunc(func(args []ParsedData) error { - t.Logf("whitelist add: %v", args) - return nil - })).Unhandle()). - AppendLiteral(g.Literal("remove"). - AppendArgument(g.Argument("targets", StringParser(0)). - HandleFunc(func(args []ParsedData) error { - t.Logf("whitelist remove: %v", args) - return nil - })).Unhandle()). - AppendLiteral(g.Literal("on"). - HandleFunc(func(args []ParsedData) error { - t.Logf("whitelist on: %v", args) - return nil - })). - AppendLiteral(g.Literal("off"). - HandleFunc(func(args []ParsedData) error { - t.Logf("whitelist off: %v", args) - return nil - })). + g.AppendLiteral(g.Literal("me"). + AppendArgument(g.Argument("action", StringParser(2)). + HandleFunc(handleFunc)). Unhandle(), + ).AppendLiteral(g.Literal("help"). + AppendArgument(g.Argument("command", StringParser(0)). + HandleFunc(handleFunc)). + HandleFunc(handleFunc), + ).AppendLiteral(g.Literal("list"). + AppendLiteral(g.Literal("uuids"). + HandleFunc(handleFunc)). + HandleFunc(handleFunc), ) - targetB := g.Argument("targetB", StringParser(0)). - HandleFunc(func(args []ParsedData) error { - t.Logf("tp A B parsed: %v", args) - return nil - }) - g.AppendLiteral(g.Literal("tp").AppendArgument( - g.Argument("targetA", StringParser(0)). - AppendLiteral(g.Literal("from"). - AppendArgument(targetB). - Unhandle()). - AppendLiteral(g.Literal("to"). - AppendArgument(targetB). - Unhandle()). - Unhandle(), - ).Unhandle()) - - err := g.Run("tp Tnze to Xi_Xi_Mi") + err := g.Run("me Tnze Xi_Xi_Mi") if err != nil { t.Fatal(err) } - - //g2 := NewGraph() - //g2.Then( - // g.Lite("whitelist").Then( - // // using reflect to generate all arguments nodes - // g.Lite("add").Then(g.Func(func(player GameProfile) { - // - // })), - // ), - //) } diff --git a/server/command/serialize.go b/server/command/serialize.go new file mode 100644 index 0000000..236a5d2 --- /dev/null +++ b/server/command/serialize.go @@ -0,0 +1,49 @@ +package command + +import ( + "io" + "unsafe" + + pk "github.com/Tnze/go-mc/net/packet" +) + +const ( + isExecutable = 1 << (iota + 2) + hasRedirect + hasSuggestionsType +) + +func (g *Graph) WriteTo(w io.Writer) (int64, error) { + return pk.Tuple{ + pk.Array(g.nodes), + pk.VarInt(0), + }.WriteTo(w) +} + +func (n Node) WriteTo(w io.Writer) (int64, error) { + var flag byte + flag |= n.kind & 0x03 + if n.Run != nil { + flag |= isExecutable + } + return pk.Tuple{ + pk.Byte(flag), + pk.Array((*[]pk.VarInt)(unsafe.Pointer(&n.Children))), + pk.Opt{ + Has: func() bool { return n.kind&hasRedirect != 0 }, + Field: nil, // TODO: send redirect node + }, + pk.Opt{ + Has: func() bool { return n.kind == ArgumentNode || n.kind == LiteralNode }, + Field: pk.String(n.Name), + }, + pk.Opt{ + Has: func() bool { return n.kind == ArgumentNode }, + Field: n.Parser, // Parser identifier and Properties + }, + pk.Opt{ + Has: func() bool { return flag&hasSuggestionsType != 0 }, + Field: nil, // TODO: send Suggestions type + }, + }.WriteTo(w) +} diff --git a/server/dimension.go b/server/dimension.go index b148b43..c539693 100644 --- a/server/dimension.go +++ b/server/dimension.go @@ -50,13 +50,10 @@ func (s *SimpleDim) PlayerJoin(p *Player) { ) column.Unlock() - err := p.WritePacket(Packet757(packet)) - if err != nil { - return - } + p.WritePacket(Packet757(packet)) } - err := p.WritePacket(Packet757(pk.Marshal( + p.WritePacket(Packet757(pk.Marshal( packetid.ClientboundPlayerPosition, pk.Double(0), pk.Double(143), pk.Double(0), pk.Float(0), pk.Float(0), @@ -64,9 +61,6 @@ func (s *SimpleDim) PlayerJoin(p *Player) { pk.VarInt(0), pk.Boolean(true), ))) - if err != nil { - return - } } func (s *SimpleDim) PlayerQuit(p *Player) { diff --git a/server/gameplay.go b/server/gameplay.go index 60a5e8b..aa1103b 100644 --- a/server/gameplay.go +++ b/server/gameplay.go @@ -80,15 +80,16 @@ func (g *Game) Run(ctx context.Context) { func (g *Game) AcceptPlayer(name string, id uuid.UUID, protocol int32, conn *net.Conn) { p := &Player{ - Conn: conn, - Name: name, - UUID: id, - EntityID: g.newEID(), - Gamemode: 1, - errChan: make(chan error, 1), + Conn: conn, + Name: name, + UUID: id, + EntityID: g.newEID(), + Gamemode: 1, + packetQueue: NewPacketQueue(), + errChan: make(chan error, 1), } dimInfo := g.Dim.Info() - err := p.WritePacket(Packet757(pk.Marshal( + err := p.Conn.WritePacket(pk.Marshal( packetid.ClientboundLogin, pk.Int(p.EntityID), // Entity ID pk.Boolean(false), // Is hardcore @@ -106,10 +107,26 @@ func (g *Game) AcceptPlayer(name string, id uuid.UUID, protocol int32, conn *net pk.Boolean(true), // Enable respawn screen pk.Boolean(false), // Is Debug pk.Boolean(true), // Is Flat - ))) + )) if err != nil { return } + + go func() { + for { + packet, ok := p.packetQueue.Pull() + if !ok { + break + } + err := p.Conn.WritePacket(packet) + if err != nil { + p.PutErr(err) + break + } + } + }() + defer p.packetQueue.Close() + g.Dim.PlayerJoin(p) defer g.Dim.PlayerQuit(p) diff --git a/server/keepalive.go b/server/keepalive.go index 03cf7e5..a34787b 100644 --- a/server/keepalive.go +++ b/server/keepalive.go @@ -121,14 +121,10 @@ func (k *KeepAlive) pingPlayer(now time.Time) { if elem := k.pingList.Front(); elem != nil { p := k.pingList.Remove(elem).(keepAliveItem).player // Send Clientbound KeepAlive packet. - err := p.WritePacket(Packet757(pk.Marshal( + p.WritePacket(Packet757(pk.Marshal( packetid.ClientboundKeepAlive, pk.Long(k.keepAliveID), ))) - if err != nil { - p.PutErr(err) - return - } k.keepAliveID++ // Clientbound KeepAlive packet is sent, move the player to waiting list. k.listIndex[p.UUID] = k.waitList.PushBack( diff --git a/server/player.go b/server/player.go index 95181b6..9fbb418 100644 --- a/server/player.go +++ b/server/player.go @@ -1,25 +1,25 @@ package server import ( + "container/list" + "github.com/google/uuid" "strconv" "sync" - "github.com/google/uuid" - "github.com/Tnze/go-mc/net" pk "github.com/Tnze/go-mc/net/packet" ) type Player struct { *net.Conn - writeLock sync.Mutex Name string uuid.UUID EntityID int32 Gamemode byte - errChan chan error + packetQueue *PacketQueue + errChan chan error } // Packet757 is a packet in protocol 757. @@ -27,14 +27,8 @@ type Player struct { type Packet757 pk.Packet // WritePacket to player client. The type of parameter will update per version. -func (p *Player) WritePacket(packet Packet757) error { - p.writeLock.Lock() - defer p.writeLock.Unlock() - err := p.Conn.WritePacket(pk.Packet(packet)) - if err != nil { - return WritePacketError{Err: err, ID: packet.ID} - } - return nil +func (p *Player) WritePacket(packet Packet757) { + p.packetQueue.Push(pk.Packet(packet)) } type WritePacketError struct { @@ -66,3 +60,47 @@ func (p *Player) GetErr() error { return nil } } + +type PacketQueue struct { + queue *list.List + closed bool + cond sync.Cond +} + +func NewPacketQueue() (p *PacketQueue) { + p = &PacketQueue{ + queue: list.New(), + cond: sync.Cond{L: new(sync.Mutex)}, + } + return p +} + +func (p *PacketQueue) Push(packet pk.Packet) { + p.cond.L.Lock() + if !p.closed { + p.queue.PushBack(packet) + } + p.cond.Signal() + p.cond.L.Unlock() +} + +func (p *PacketQueue) Pull() (packet pk.Packet, ok bool) { + p.cond.L.Lock() + defer p.cond.L.Unlock() + for p.queue.Front() == nil && !p.closed { + p.cond.Wait() + } + if p.closed { + return pk.Packet{}, false + } + packet = p.queue.Remove(p.queue.Front()).(pk.Packet) + ok = true + return +} + +func (p *PacketQueue) Close() { + p.cond.L.Lock() + p.closed = true + p.cond.Broadcast() + p.cond.L.Unlock() +} diff --git a/server/playerlist.go b/server/playerlist.go index 0e50b71..42231a6 100644 --- a/server/playerlist.go +++ b/server/playerlist.go @@ -42,15 +42,11 @@ func (p *PlayerList) AddPlayer(player *Player) { defer p.playersLock.Unlock() if len(p.players) >= p.maxPlayer { - err := player.WritePacket(Packet757(pk.Marshal( + player.WritePacket(Packet757(pk.Marshal( packetid.ClientboundDisconnect, chat.TranslateMsg("multiplayer.disconnect.server_full"), ))) - if err != nil { - player.PutErr(err) - } else { - player.PutErr(errors.New("playerlist: server full")) - } + player.PutErr(errors.New("playerlist: server full")) return }