player.go 3.68 KB
Newer Older
1
2
3
4
5
package main

import (
	tg "fahrradwurstmond2.0/telnet_gui"
	"log"
Michael Gebhard's avatar
go fmt    
Michael Gebhard committed
6
	"net"
7
	"os"
Michael Gebhard's avatar
go fmt    
Michael Gebhard committed
8
	"time"
9
	//"errors"
10
11
12
13
)

type Context interface {
	Register(*Player)
ceddral's avatar
ceddral committed
14
	Resize(int, int, *Player)
ceddral's avatar
ceddral committed
15
16
17
	Redraw(*Player)
	Lock()
	Unlock()
18
19
20
}

type Player struct {
Michael Gebhard's avatar
go fmt    
Michael Gebhard committed
21
22
	tnState    *TelnetState
	conn       net.Conn
23
	connClosed chan struct{}
24
	inputEvent chan []byte
Michael Gebhard's avatar
go fmt    
Michael Gebhard committed
25
	inputKill  chan struct{}
26
	windowSizeLoopKill  chan struct{}
Michael Gebhard's avatar
go fmt    
Michael Gebhard committed
27
28
29
	name       string
	context    Context
	scr        *tg.Screen
30
31
32
}

func NewPlayer(conn net.Conn) (p *Player) {
33
34
35
	tcpConn := conn.(*net.TCPConn)
	tcpConn.SetKeepAlivePeriod(time.Second)
	tcpConn.SetKeepAlive(true)
36
37
	var err error
	p = &Player{
Michael Gebhard's avatar
go fmt    
Michael Gebhard committed
38
		conn:       conn,
39
		connClosed: make(chan struct{}, 1),
Michael Gebhard's avatar
go fmt    
Michael Gebhard committed
40
		tnState:    NewTelnetState(),
41
		inputEvent: make(chan []byte, 50),
Michael Gebhard's avatar
go fmt    
Michael Gebhard committed
42
		inputKill:  make(chan struct{}),
43
	}
44
	go p.inputLoop()
45
	p.name, err = queryUsername(p.conn, p.tnState)
46
47
48
	if err != nil {
		log.Println(err)
	}
Jonny Schäfer's avatar
Jonny Schäfer committed
49

50
	if pOld, ok := gs.players[p.name]; ok {
Jonny Schäfer's avatar
Jonny Schäfer committed
51
		// reconnect with replace
52
		pNew := p
53
		// check if old player is still responsive
54
55
56
57
		select {
		case <-pOld.connClosed:
			// continue
		default:
58
59
60
61
62
63
64
65
			// old player is responsive, disconnect imposter
			pNew.inputKill <- struct{}{}
			close(pNew.inputKill)
			close(pNew.inputEvent)
			conn.Write([]byte("Username already taken\n"))
			conn.Close()
			return nil
		}
66
67
68

		// kill old player components to be replaced
		pOld.inputKill <- struct{}{}
69
		pOld.conn.Close()
70
71
72

		// clean up new player input, was only needed to query username
		pNew.inputKill <- struct{}{}
73
		close(pNew.connClosed)
74
75
76
77
		close(pNew.inputKill)
		close(pNew.inputEvent)

		// assemble reconnected player
78
		p = pOld
79
		// no inputLoop for pOld and pNew may be running during these modifications to p
80
81
		p.conn = pNew.conn
		p.scr.Chown(conn)
82
		go p.inputLoop()
83
	} else {
84
		p.windowSizeLoopKill = make(chan struct{})
85
86
87
88
		gs.players[p.name] = p
		// TODO
		// new players need to be sent to the meta lobby context
		panic("TODO Connect for new players not implemented yet")
89
	}
ceddral's avatar
ceddral committed
90
91
92
93
	err = p.scr.Redraw()
	if err != nil {
		log.Println(err)
	}
94
	return p
95
}
96
97
98
99
100
101
102
103
104
105

func StopTimer(t *time.Timer) {
	if !t.Stop() {
		select {
		case <-t.C:
		default:
		}
	}
}

106
func (p *Player) inputLoop() {
107
	key := make([]byte, 0, 3)
108
	p.conn.SetReadDeadline(time.Now().Add(time.Second))
109
	for {
Jonny Schäfer's avatar
Jonny Schäfer committed
110
		select {
111
112
		// kill loop without closing connection in case of reconnect
		case <-p.inputKill:
Michael Gebhard's avatar
go fmt    
Michael Gebhard committed
113
			return
Jonny Schäfer's avatar
Jonny Schäfer committed
114
115
116
		default:
		}

117
		if 0 == len(key) {
118
			p.conn.SetReadDeadline(time.Now().Add(time.Second))
119
		}
120
		b, err := readByteTelnet(p.conn, p.tnState)
121
		if err != nil {
Michael Gebhard's avatar
go fmt    
Michael Gebhard committed
122
			if os.IsTimeout(err) { //errors.Is(err, os.ErrDeadlineExceeded) {
123
124
125
126
127
				if 0 < len(key) {
					goto out
				}
				continue
			}
128
129
130
			p.connClosed <- struct{}{}
			// await termination, to unblock a reconnect
			<-p.inputKill
131
132
			return
		}
133
134
		key = append(key, b)
		if b == 27 { // ESC
135
			p.conn.SetReadDeadline(time.Now().Add(connectionConfig.ANSIEscapeTimeout * time.Millisecond))
136
137
138
139
140
			continue
		}
		if 1 < len(key) {
			continue
		}
Michael Gebhard's avatar
go fmt    
Michael Gebhard committed
141
	out:
142
143
		if toKey(key) != keyNull {
			log.Printf("key %#v\n", key)
144
			select {
145
			case p.inputEvent <- key:
146
147
148
			default:
				// discard inputs if they come too fast
			}
149
		}
150
151
152
		key = make([]byte, 0, 3)
	}
}
153
154
155

func (p *Player) windowSizeLoop() {
	p.tnState.Lock()
156
157
158
159
	defer func() {
		p.tnState.Unlock()
		<-p.windowSizeLoopKill
	}()
160
	for {
161
162
163
164
165
		select {
		case <-p.windowSizeLoopKill:
			return
		default:
		}
166
		timeoutChan := cond_set_timeout(p.tnState.lock, p.tnState.cond, time.Second)
167
		pthread_cond_timedwait_because_fuck_you(p.tnState.cond, timeoutChan)
168
169
170
171
172
		w := p.tnState.windowWidth
		h := p.tnState.windowHeight
		if p.scr.W == w && p.scr.H == h {
			continue
		}
ceddral's avatar
ceddral committed
173
		gs.Lock()
ceddral's avatar
ceddral committed
174
		p.context.Lock()
175
		p.scr.Resize(w, h)
ceddral's avatar
ceddral committed
176
		p.context.Resize(w, h, p)
ceddral's avatar
ceddral committed
177
178
		p.context.Redraw(p)
		p.context.Unlock()
ceddral's avatar
ceddral committed
179
		gs.Unlock()
180
181
	}
}