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