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