-
Notifications
You must be signed in to change notification settings - Fork 110
/
input.go
209 lines (175 loc) · 5.53 KB
/
input.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
// Package fake implements a fake input controller.
package fake
import (
"context"
"math/rand"
"sync"
"time"
"github.com/edaniels/golog"
"github.com/pkg/errors"
"go.viam.com/utils"
"go.viam.com/rdk/components/input"
"go.viam.com/rdk/resource"
)
var model = resource.DefaultModelFamily.WithModel("fake")
func init() {
resource.RegisterComponent(
input.API,
model,
resource.Registration[input.Controller, *Config]{
Constructor: func(ctx context.Context, _ resource.Dependencies, conf resource.Config, _ golog.Logger) (input.Controller, error) {
return NewInputController(ctx, conf)
},
},
)
}
// Config can list input structs (with their states), define event values and callback delays.
type Config struct {
resource.TriviallyValidateConfig
controls []input.Control
// EventValue will dictate the value of the events returned. Random between -1 to 1 if unset.
EventValue *float64 `json:"event_value,omitempty"`
// CallbackDelaySec is the amount of time between callbacks getting triggered. Random between (1-2] sec if unset.
// 0 is not valid and will be overwritten by a random delay.
CallbackDelaySec float64 `json:"callback_delay_sec"`
}
type callback struct {
control input.Control
triggers []input.EventType
ctrlFunc input.ControlFunction
}
// NewInputController returns a fake input.Controller.
func NewInputController(ctx context.Context, conf resource.Config) (input.Controller, error) {
closeCtx, cancelFunc := context.WithCancel(ctx)
c := &InputController{
Named: conf.ResourceName().AsNamed(),
closeCtx: closeCtx,
cancelFunc: cancelFunc,
callbacks: make([]callback, 0),
}
if err := c.Reconfigure(ctx, nil, conf); err != nil {
return nil, err
}
// start callback thread
c.activeBackgroundWorkers.Add(1)
utils.ManagedGo(func() {
c.startCallbackLoop()
}, c.activeBackgroundWorkers.Done)
return c, nil
}
// An InputController fakes an input.Controller.
type InputController struct {
resource.Named
closeCtx context.Context
cancelFunc func()
activeBackgroundWorkers sync.WaitGroup
mu sync.Mutex
controls []input.Control
eventValue *float64
callbackDelay *time.Duration
callbacks []callback
}
// Reconfigure updates the config of the controller.
func (c *InputController) Reconfigure(ctx context.Context, deps resource.Dependencies, conf resource.Config) error {
newConf, err := resource.NativeConfig[*Config](conf)
if err != nil {
return err
}
c.mu.Lock()
defer c.mu.Unlock()
c.controls = newConf.controls
c.eventValue = newConf.EventValue
if newConf.CallbackDelaySec != 0 {
// convert to milliseconds to avoid any issues with float to int conversions
delay := time.Duration(newConf.CallbackDelaySec*1000) * time.Millisecond
c.callbackDelay = &delay
}
return nil
}
// Controls lists the inputs of the gamepad.
func (c *InputController) Controls(ctx context.Context, extra map[string]interface{}) ([]input.Control, error) {
c.mu.Lock()
defer c.mu.Unlock()
if len(c.controls) == 0 {
return []input.Control{input.AbsoluteX, input.ButtonStart}, nil
}
return c.controls, nil
}
func (c *InputController) eventVal() float64 {
if c.eventValue != nil {
return *c.eventValue
}
//nolint:gosec
return rand.Float64()
}
// Events returns the a specified or random input.Event (the current state) for AbsoluteX.
func (c *InputController) Events(ctx context.Context, extra map[string]interface{}) (map[input.Control]input.Event, error) {
c.mu.Lock()
defer c.mu.Unlock()
eventsOut := make(map[input.Control]input.Event)
eventsOut[input.AbsoluteX] = input.Event{Time: time.Now(), Event: input.PositionChangeAbs, Control: input.AbsoluteX, Value: c.eventVal()}
return eventsOut, nil
}
// RegisterControlCallback registers a callback function to be executed on the specified trigger Event. The fake implementation will
// trigger the callback at a random or user-specified interval with a random or user-specified value.
func (c *InputController) RegisterControlCallback(
ctx context.Context,
control input.Control,
triggers []input.EventType,
ctrlFunc input.ControlFunction,
extra map[string]interface{},
) error {
c.mu.Lock()
defer c.mu.Unlock()
c.callbacks = append(c.callbacks, callback{control: control, triggers: triggers, ctrlFunc: ctrlFunc})
return nil
}
func (c *InputController) startCallbackLoop() {
for {
var callbackDelay time.Duration
if c.closeCtx.Err() != nil {
return
}
c.mu.Lock()
if c.callbackDelay != nil {
callbackDelay = *c.callbackDelay
} else {
//nolint:gosec
callbackDelay = 1*time.Second + time.Duration(rand.Float64()*1000)*time.Millisecond
}
c.mu.Unlock()
if !utils.SelectContextOrWait(c.closeCtx, callbackDelay) {
return
}
select {
case <-c.closeCtx.Done():
return
default:
c.mu.Lock()
evValue := c.eventVal()
for _, callback := range c.callbacks {
for _, t := range callback.triggers {
event := input.Event{Time: time.Now(), Event: t, Control: callback.control, Value: evValue}
callback.ctrlFunc(c.closeCtx, event)
}
}
c.mu.Unlock()
}
}
}
// TriggerEvent allows directly sending an Event (such as a button press) from external code.
func (c *InputController) TriggerEvent(ctx context.Context, event input.Event, extra map[string]interface{}) error {
return errors.New("unsupported")
}
// Close attempts to cleanly close the input controller.
func (c *InputController) Close(ctx context.Context) error {
c.mu.Lock()
var err error
if c.cancelFunc != nil {
c.cancelFunc()
c.cancelFunc = nil
}
c.mu.Unlock()
c.activeBackgroundWorkers.Wait()
return err
}