1
0
Fork 0
mirror of https://github.com/dragonfireclient/hydra-dragonfire.git synced 2024-11-21 10:24:01 -05:00
hydra-dragonfire/client.go
2022-06-01 18:09:48 +02:00

257 lines
4.7 KiB
Go

package main
import (
"errors"
"github.com/anon55555/mt"
"github.com/dragonfireclient/hydra-dragonfire/convert"
"github.com/yuin/gopher-lua"
"net"
"sync"
)
type clientState uint8
const (
csNew clientState = iota
csConnected
csDisconnected
)
type Component interface {
create(client *Client, l *lua.LState)
push() lua.LValue
connect()
process(pkt *mt.Pkt)
}
type Client struct {
mu sync.Mutex
address string
state clientState
conn mt.Peer
queue chan Event
components map[string]Component
table *lua.LTable
userdata *lua.LUserData
}
var clientFuncs = map[string]lua.LGFunction{
"address": l_client_address,
"state": l_client_state,
"connect": l_client_connect,
"poll": l_client_poll,
"close": l_client_close,
"enable": l_client_enable,
"send": l_client_send,
}
type EventError struct {
err string
}
func (evt EventError) handle(l *lua.LState, val lua.LValue) {
l.SetField(val, "type", lua.LString("error"))
l.SetField(val, "error", lua.LString(evt.err))
}
type EventDisconnect struct {
client *Client
}
func (evt EventDisconnect) handle(l *lua.LState, val lua.LValue) {
l.SetField(val, "type", lua.LString("disconnect"))
evt.client.state = csDisconnected
}
func getClient(l *lua.LState) *Client {
return l.CheckUserData(1).Value.(*Client)
}
func getClients(l *lua.LState) []*Client {
tbl := l.CheckTable(1)
n := tbl.MaxN()
clients := make([]*Client, 0, n)
for i := 1; i <= n; i++ {
clients = append(clients, l.RawGetInt(tbl, i).(*lua.LUserData).Value.(*Client))
}
return clients
}
func (client *Client) closeConn() {
client.mu.Lock()
defer client.mu.Unlock()
if client.state == csConnected {
client.conn.Close()
}
}
func l_client(l *lua.LState) int {
client := &Client{}
client.address = l.CheckString(1)
client.state = csNew
client.components = map[string]Component{}
client.table = l.NewTable()
client.userdata = l.NewUserData()
client.userdata.Value = client
l.SetMetatable(client.userdata, l.GetTypeMetatable("hydra.client"))
l.Push(client.userdata)
return 1
}
func l_client_index(l *lua.LState) int {
client := getClient(l)
key := l.CheckString(2)
if key == "data" {
l.Push(client.table)
} else if fun, exists := clientFuncs[key]; exists {
l.Push(l.NewFunction(fun))
} else if component, exists := client.components[key]; exists {
l.Push(component.push())
} else {
l.Push(lua.LNil)
}
return 1
}
func l_client_address(l *lua.LState) int {
client := getClient(l)
l.Push(lua.LString(client.address))
return 1
}
func l_client_state(l *lua.LState) int {
client := getClient(l)
switch client.state {
case csNew:
l.Push(lua.LString("new"))
case csConnected:
l.Push(lua.LString("connected"))
case csDisconnected:
l.Push(lua.LString("disconnected"))
}
return 1
}
func l_client_connect(l *lua.LState) int {
client := getClient(l)
if client.state != csNew {
panic("can't reconnect")
}
addr, err := net.ResolveUDPAddr("udp", client.address)
if err != nil {
panic(err)
}
conn, err := net.DialUDP("udp", nil, addr)
if err != nil {
panic(err)
}
client.state = csConnected
client.conn = mt.Connect(conn)
client.queue = make(chan Event, 1024)
go func() {
for {
pkt, err := client.conn.Recv()
if err == nil {
client.mu.Lock()
for _, comp := range client.components {
comp.process(&pkt)
}
client.mu.Unlock()
} else if errors.Is(err, net.ErrClosed) {
client.queue <- EventDisconnect{client: client}
return
} else {
client.queue <- EventError{err: err.Error()}
}
}
}()
client.mu.Lock()
for _, comp := range client.components {
comp.connect()
}
client.mu.Unlock()
return 0
}
func l_client_poll(l *lua.LState) int {
client := getClient(l)
return doPoll(l, []*Client{client})
}
func l_client_close(l *lua.LState) int {
client := getClient(l)
client.closeConn()
return 0
}
func l_client_enable(l *lua.LState) int {
client := getClient(l)
n := l.GetTop()
client.mu.Lock()
defer client.mu.Unlock()
for i := 2; i <= n; i++ {
name := l.CheckString(i)
if comp, exists := client.components[name]; !exists {
switch name {
case "auth":
comp = &CompAuth{}
case "map":
comp = &CompMap{}
case "pkts":
comp = &CompPkts{}
default:
panic("invalid component: " + name)
}
client.components[name] = comp
comp.create(client, l)
}
}
return 0
}
func l_client_send(l *lua.LState) int {
client := getClient(l)
if client.state != csConnected {
panic("not connected")
}
cmd := convert.ReadCmd(l)
doAck := l.ToBool(4)
client.mu.Lock()
defer client.mu.Unlock()
if client.state == csConnected {
ack, err := client.conn.SendCmd(cmd)
if err != nil && !errors.Is(err, net.ErrClosed) {
panic(err)
}
if doAck && !cmd.DefaultPktInfo().Unrel {
<-ack
}
}
return 0
}