]> git.lizzy.rs Git - hydra-dragonfire.git/blob - client.go
Event system
[hydra-dragonfire.git] / client.go
1 package main
2
3 import (
4         "errors"
5         "github.com/anon55555/mt"
6         "github.com/dragonfireclient/hydra-dragonfire/convert"
7         "github.com/yuin/gopher-lua"
8         "net"
9         "sync"
10 )
11
12 type clientState uint8
13
14 const (
15         csNew clientState = iota
16         csConnected
17         csDisconnected
18 )
19
20 type Component interface {
21         create(client *Client, l *lua.LState)
22         push() lua.LValue
23         connect()
24         process(pkt *mt.Pkt)
25 }
26
27 type Client struct {
28         mu         sync.Mutex
29         address    string
30         state      clientState
31         conn       mt.Peer
32         queue      chan Event
33         components map[string]Component
34         table      *lua.LTable
35         userdata   *lua.LUserData
36 }
37
38 var clientFuncs = map[string]lua.LGFunction{
39         "address": l_client_address,
40         "state":   l_client_state,
41         "connect": l_client_connect,
42         "poll":    l_client_poll,
43         "close":   l_client_close,
44         "enable":  l_client_enable,
45         "send":    l_client_send,
46 }
47
48 type EventError struct {
49         err string
50 }
51
52 func (evt EventError) handle(l *lua.LState, val lua.LValue) {
53         l.SetField(val, "type", lua.LString("error"))
54         l.SetField(val, "error", lua.LString(evt.err))
55 }
56
57 type EventDisconnect struct {
58         client *Client
59 }
60
61 func (evt EventDisconnect) handle(l *lua.LState, val lua.LValue) {
62         l.SetField(val, "type", lua.LString("disconnect"))
63         evt.client.state = csDisconnected
64 }
65
66 func getClient(l *lua.LState) *Client {
67         return l.CheckUserData(1).Value.(*Client)
68 }
69
70 func getClients(l *lua.LState) []*Client {
71         tbl := l.CheckTable(1)
72         n := tbl.MaxN()
73
74         clients := make([]*Client, 0, n)
75         for i := 1; i <= n; i++ {
76                 clients = append(clients, l.RawGetInt(tbl, i).(*lua.LUserData).Value.(*Client))
77         }
78
79         return clients
80 }
81
82 func (client *Client) closeConn() {
83         client.mu.Lock()
84         defer client.mu.Unlock()
85
86         if client.state == csConnected {
87                 client.conn.Close()
88         }
89 }
90
91 func l_client(l *lua.LState) int {
92         client := &Client{}
93
94         client.address = l.CheckString(1)
95         client.state = csNew
96         client.components = map[string]Component{}
97         client.table = l.NewTable()
98         client.userdata = l.NewUserData()
99         client.userdata.Value = client
100         l.SetMetatable(client.userdata, l.GetTypeMetatable("hydra.client"))
101
102         l.Push(client.userdata)
103         return 1
104 }
105
106 func l_client_index(l *lua.LState) int {
107         client := getClient(l)
108         key := l.CheckString(2)
109
110         if key == "data" {
111                 l.Push(client.table)
112         } else if fun, exists := clientFuncs[key]; exists {
113                 l.Push(l.NewFunction(fun))
114         } else if component, exists := client.components[key]; exists {
115                 l.Push(component.push())
116         } else {
117                 l.Push(lua.LNil)
118         }
119
120         return 1
121 }
122
123 func l_client_address(l *lua.LState) int {
124         client := getClient(l)
125         l.Push(lua.LString(client.address))
126         return 1
127 }
128
129 func l_client_state(l *lua.LState) int {
130         client := getClient(l)
131         switch client.state {
132         case csNew:
133                 l.Push(lua.LString("new"))
134         case csConnected:
135                 l.Push(lua.LString("connected"))
136         case csDisconnected:
137                 l.Push(lua.LString("disconnected"))
138         }
139         return 1
140 }
141
142 func l_client_connect(l *lua.LState) int {
143         client := getClient(l)
144
145         if client.state != csNew {
146                 panic("can't reconnect")
147         }
148
149         addr, err := net.ResolveUDPAddr("udp", client.address)
150         if err != nil {
151                 panic(err)
152         }
153
154         conn, err := net.DialUDP("udp", nil, addr)
155         if err != nil {
156                 panic(err)
157         }
158
159         client.state = csConnected
160         client.conn = mt.Connect(conn)
161         client.queue = make(chan Event, 1024)
162
163         go func() {
164                 for {
165                         pkt, err := client.conn.Recv()
166
167                         if err == nil {
168                                 client.mu.Lock()
169                                 for _, component := range client.components {
170                                         component.process(&pkt)
171                                 }
172                                 client.mu.Unlock()
173                         } else if errors.Is(err, net.ErrClosed) {
174                                 client.queue <- EventDisconnect{client: client}
175                                 return
176                         } else {
177                                 client.queue <- EventError{err: err.Error()}
178                         }
179                 }
180         }()
181
182         client.mu.Lock()
183         for _, component := range client.components {
184                 component.connect()
185         }
186         client.mu.Unlock()
187
188         return 0
189 }
190
191 func l_client_poll(l *lua.LState) int {
192         client := getClient(l)
193         return doPoll(l, []*Client{client})
194 }
195
196 func l_client_close(l *lua.LState) int {
197         client := getClient(l)
198         client.closeConn()
199         return 0
200 }
201
202 func l_client_enable(l *lua.LState) int {
203         client := getClient(l)
204         n := l.GetTop()
205
206         client.mu.Lock()
207         defer client.mu.Unlock()
208
209         for i := 2; i <= n; i++ {
210                 compname := l.CheckString(i)
211
212                 if component, exists := client.components[compname]; !exists {
213                         switch compname {
214                         case "auth":
215                                 component = &Auth{}
216                         case "map":
217                                 component = &Map{}
218                         case "pkts":
219                                 component = &Pkts{}
220                         default:
221                                 panic("invalid component: " + compname)
222                         }
223
224                         client.components[compname] = component
225                         component.create(client, l)
226                 }
227         }
228
229         return 0
230 }
231
232 func l_client_send(l *lua.LState) int {
233         client := getClient(l)
234
235         if client.state != csConnected {
236                 panic("not connected")
237         }
238
239         cmd := convert.ReadCmd(l)
240         doAck := l.ToBool(4)
241
242         client.mu.Lock()
243         defer client.mu.Unlock()
244
245         if client.state == csConnected {
246                 ack, err := client.conn.SendCmd(cmd)
247                 if err != nil && !errors.Is(err, net.ErrClosed) {
248                         panic(err)
249                 }
250
251                 if doAck && !cmd.DefaultPktInfo().Unrel {
252                         <-ack
253                 }
254         }
255
256         return 0
257 }