Move command component

This commit is contained in:
Tnze
2022-01-04 12:23:48 +08:00
parent be3a834696
commit badf77d219
5 changed files with 124 additions and 125 deletions

View File

@ -1,49 +0,0 @@
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) {}

View File

@ -5,50 +5,48 @@ func (g *Graph) AppendLiteral(child *Literal) *Graph {
return g return g
} }
// Literal create a new LiteralNode in the Graph.
func (g *Graph) Literal(str string) LiteralBuilder { func (g *Graph) Literal(str string) LiteralBuilder {
index := int32(len(g.nodes)) index := int32(len(g.nodes))
g.nodes = append(g.nodes, Node{ g.nodes = append(g.nodes, &Node{
g: g, g: g,
index: index, index: index,
kind: LiteralNode, kind: LiteralNode,
Name: str, Name: str,
}) })
return LiteralBuilder{g: g, current: index} return LiteralBuilder{current: g.nodes[index]}
} }
// Argument create a new ArgumentNode in the Graph.
func (g *Graph) Argument(name string, parser Parser) ArgumentBuilder { func (g *Graph) Argument(name string, parser Parser) ArgumentBuilder {
index := int32(len(g.nodes)) index := int32(len(g.nodes))
g.nodes = append(g.nodes, Node{ g.nodes = append(g.nodes, &Node{
g: g, g: g,
index: index, index: index,
kind: ArgumentNode, kind: ArgumentNode,
Name: name, Name: name,
Parser: parser, Parser: parser,
}) })
return ArgumentBuilder{g: g, current: index} return ArgumentBuilder{current: g.nodes[index]}
} }
type LiteralBuilder struct { type LiteralBuilder struct {
g *Graph current *Node
current int32
} }
func (n LiteralBuilder) AppendLiteral(node *Literal) LiteralBuilderWithLiteral { func (n LiteralBuilder) AppendLiteral(node *Literal) LiteralBuilderWithLiteral {
current := &n.g.nodes[n.current] n.current.Children = append(n.current.Children, node.index)
current.Children = append(current.Children, node.index)
return LiteralBuilderWithLiteral{n: n} return LiteralBuilderWithLiteral{n: n}
} }
func (n LiteralBuilder) AppendArgument(node *Argument) LiteralBuilderWithArgument { func (n LiteralBuilder) AppendArgument(node *Argument) LiteralBuilderWithArgument {
current := &n.g.nodes[n.current] n.current.Children = append(n.current.Children, node.index)
current.Children = append(current.Children, node.index)
return LiteralBuilderWithArgument{n: n} return LiteralBuilderWithArgument{n: n}
} }
func (n LiteralBuilder) HandleFunc(f HandlerFunc) *Literal { func (n LiteralBuilder) HandleFunc(f HandlerFunc) *Literal {
current := &n.g.nodes[n.current] n.current.Run = f
current.Run = f return (*Literal)(n.current)
return (*Literal)(current)
} }
func (n LiteralBuilder) Unhandle() *Literal { func (n LiteralBuilder) Unhandle() *Literal {
@ -56,26 +54,22 @@ func (n LiteralBuilder) Unhandle() *Literal {
} }
type ArgumentBuilder struct { type ArgumentBuilder struct {
g *Graph current *Node
current int32
} }
func (n ArgumentBuilder) AppendLiteral(node *Literal) ArgumentBuilderWithLiteral { func (n ArgumentBuilder) AppendLiteral(node *Literal) ArgumentBuilderWithLiteral {
current := &n.g.nodes[n.current] n.current.Children = append(n.current.Children, node.index)
current.Children = append(current.Children, node.index)
return ArgumentBuilderWithLiteral{n: n} return ArgumentBuilderWithLiteral{n: n}
} }
func (n ArgumentBuilder) AppendArgument(node *Argument) ArgumentBuilderWithArgument { func (n ArgumentBuilder) AppendArgument(node *Argument) ArgumentBuilderWithArgument {
current := &n.g.nodes[n.current] n.current.Children = append(n.current.Children, node.index)
current.Children = append(current.Children, node.index)
return ArgumentBuilderWithArgument{n: n} return ArgumentBuilderWithArgument{n: n}
} }
func (n ArgumentBuilder) HandleFunc(f HandlerFunc) *Argument { func (n ArgumentBuilder) HandleFunc(f HandlerFunc) *Argument {
current := &n.g.nodes[n.current] n.current.Run = f
current.Run = f return (*Argument)(n.current)
return (*Argument)(current)
} }
func (n ArgumentBuilder) Unhandle() *Argument { func (n ArgumentBuilder) Unhandle() *Argument {
@ -103,11 +97,11 @@ type LiteralBuilderWithArgument struct {
} }
func (n LiteralBuilderWithArgument) HandleFunc(f HandlerFunc) *Literal { func (n LiteralBuilderWithArgument) HandleFunc(f HandlerFunc) *Literal {
return (*Literal)((*Argument)(n.n.HandleFunc(f))) return n.n.HandleFunc(f)
} }
func (n LiteralBuilderWithArgument) Unhandle() *Literal { func (n LiteralBuilderWithArgument) Unhandle() *Literal {
return (*Literal)((*Argument)(n.n.Unhandle())) return n.n.Unhandle()
} }
type ArgumentBuilderWithLiteral struct { type ArgumentBuilderWithLiteral struct {

View File

@ -1,6 +1,7 @@
package command package command
import ( import (
"context"
"errors" "errors"
"strings" "strings"
) )
@ -11,54 +12,58 @@ const (
ArgumentNode ArgumentNode
) )
// Graph is a directed graph with a root node, representing all commands and how they are parsed.
type Graph struct { type Graph struct {
nodes []Node // List of all nodes. The first element is the root node
nodes []*Node
} }
func NewGraph() *Graph { func NewGraph() *Graph {
g := new(Graph) var g Graph
root := Node{ g.nodes = append(g.nodes,
g: g, &Node{g: &g, kind: RootNode},
kind: RootNode, )
} return &g
g.nodes = append(g.nodes, root)
return g
} }
func (g *Graph) Run(cmd string) error { func (g *Graph) Execute(ctx context.Context, cmd string) error {
var args []ParsedData var args []ParsedData
node := &g.nodes[0] // root node := g.nodes[0] // root
for { for {
left, value, next, err := node.parse(cmd) // parser command
left, value, err := node.parse(cmd)
if err != nil { if err != nil {
return err return err
} }
args = append(args, value) args = append(args, value)
left = strings.TrimSpace(left) left = strings.TrimSpace(left)
if len(left) == 0 { if len(left) == 0 {
err := node.Run(args) return node.Run(ctx, args)
if err != nil { }
return err // find next node
} next, err := node.next(left)
return nil if err != nil {
return err
} }
if next == 0 { if next == 0 {
return errors.New("command contains extra text: " + left) return errors.New("command contains extra text: " + left)
} }
cmd = left cmd = left
node = &g.nodes[next] node = g.nodes[next]
} }
} }
type ParsedData interface{} type ParsedData interface{}
type HandlerFunc func(args []ParsedData) error type HandlerFunc func(ctx context.Context, args []ParsedData) error
// Node is the node of the Graph. There are 3 kinds of node: Root, Literal and Argument.
type Node struct { type Node struct {
g *Graph g *Graph
index int32 index int32
kind byte kind byte
Name string Name string
Children []int32 Children []int32
SuggestionsType string SuggestionsType string
@ -68,54 +73,58 @@ type Node struct {
type Literal Node type Literal Node
type Argument Node type Argument Node
func (n *Node) parse(cmd string) (left string, value ParsedData, next int32, err error) { func (n *Node) parse(cmd string) (left string, value ParsedData, err error) {
switch n.kind & 0x03 { switch n.kind & 0x03 {
case RootNode: case RootNode:
left = cmd left = cmd
value = nil value = nil
err = nil err = nil
case LiteralNode: case LiteralNode:
if !strings.HasPrefix(cmd, n.Name) { if !strings.HasPrefix(cmd, n.Name) {
panic("expect " + cmd + " prefixed with " + n.Name) panic("expect " + cmd + " prefixed with " + n.Name)
} }
left = strings.TrimPrefix(cmd, n.Name) left = strings.TrimPrefix(cmd, n.Name)
value = LiteralData(n.Name) value = LiteralData(n.Name)
case ArgumentNode: case ArgumentNode:
left, value, err = n.Parser.Parse(cmd) left, value, err = n.Parser.Parse(cmd)
if err != nil {
return "", nil, 0, err
}
default: default:
panic("unreachable") panic("unreachable")
} }
// find next
if len(n.Children) > 0 {
// look up the first child's type
switch n.g.nodes[n.Children[0]].kind & 0x03 {
case RootNode:
panic("root node can't be child")
default:
panic("unreachable")
case LiteralNode:
_, value, err := StringParser(0).Parse(strings.TrimSpace(left))
if err != nil {
return "", nil, 0, err
}
literal := value.(string)
for _, i := range n.Children {
if n.g.nodes[i].Name == literal {
next = i
break
}
}
case ArgumentNode:
next = n.Children[0]
}
}
return return
} }
func unhandledCmd([]ParsedData) error { func (n *Node) next(left string) (next int32, err error) {
if len(n.Children) == 0 {
return 0, nil
}
// look up the first child's type
switch n.g.nodes[n.Children[0]].kind & 0x03 {
case RootNode:
panic("root node can't be child")
default:
panic("unreachable")
case LiteralNode:
_, value, err := StringParser(0).Parse(strings.TrimSpace(left))
if err != nil {
return 0, err
}
literal := value.(string)
for _, i := range n.Children {
if n.g.nodes[i].Name == literal {
next = i
break
}
}
case ArgumentNode:
next = n.Children[0]
}
return
}
func unhandledCmd(context.Context, []ParsedData) error {
return errors.New("unhandled function") return errors.New("unhandled function")
} }

View File

@ -1,12 +1,13 @@
package command package command
import ( import (
"context"
"log" "log"
"testing" "testing"
) )
func TestRoot_Run(t *testing.T) { func TestRoot_Run(t *testing.T) {
handleFunc := func(args []ParsedData) error { handleFunc := func(ctx context.Context, args []ParsedData) error {
log.Printf("Command: args: %v", args) log.Printf("Command: args: %v", args)
return nil return nil
} }
@ -25,7 +26,7 @@ func TestRoot_Run(t *testing.T) {
HandleFunc(handleFunc), HandleFunc(handleFunc),
) )
err := g.Run("me Tnze Xi_Xi_Mi") err := g.Execute(context.TODO(), "me Tnze Xi_Xi_Mi")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -0,0 +1,44 @@
package command
import (
"context"
"strings"
"github.com/Tnze/go-mc/data/packetid"
pk "github.com/Tnze/go-mc/net/packet"
"github.com/Tnze/go-mc/server"
)
// Init implement server.Component for Graph
func (g *Graph) Init(game *server.Game) {
game.AddHandler(&server.PacketHandler{
ID: packetid.ServerboundChat,
F: func(player *server.Player, packet server.Packet757) error {
var msg pk.String
if err := pk.Packet(packet).Scan(&msg); err != nil {
return err
}
if cmd := string(msg); strings.HasPrefix(cmd, "/") {
ctx := context.WithValue(context.TODO(), "sender", player)
cmderr := g.Execute(ctx, strings.TrimPrefix(cmd, "/"))
if cmderr != nil {
// TODO: tell player that their command has error
}
}
return nil
},
})
}
// Run implement server.Component for Graph
func (g *Graph) Run(ctx context.Context) {}
// AddPlayer implement server.Component for Graph
func (g *Graph) AddPlayer(p *server.Player) {
p.WritePacket(server.Packet757(pk.Marshal(
packetid.ClientboundCommands, g,
)))
}
// RemovePlayer implement server.Component for Graph
func (g *Graph) RemovePlayer(p *server.Player) {}