4ed9664f6f7fdc38525a480511049aca48fa1dbf
[nit.git] / lib / mnit / mnit_injected_input.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Injection of input events that are read frm a file.
16 # This cloud be used to replay an execution of a mnit application to debug or to benchmark it.
17 #
18 # The input file is given through the environment variable `MNIT_READ_INPUT`
19 #
20 # In order to reproduce executions, the behavior of the application must be deterministic
21 # for a given sequence of inputs.
22 # The main source of differences in executions is caused by the `rand` function,
23 # Set the environment variable `MNIT_SRAND` to a value to force srand to be initialized with this value.
24 #
25 # The input event file is made of event descriptions, one event by line.
26 #
27 # ~~~
28 # 10 click 10.0 20.0
29 # 20 quit
30 # ~~~
31 #
32 # The first field, an integer, is the delay (in frame count) since the previous event
33 # 0 means the event is launched in the same frame that the previous one.
34 #
35 # The second field, a string, is the kind of the event.
36 # Currently only `click` for PointerEvent and `quit` for QuitEvent are recognized.
37 #
38 # The following fields are the arguments that specific for each kind of event.
39 #
40 # * `quit` does not have arguments
41 # * `click` has 2 float arguments: `PointerEvent::x` and `PointerEvent::y`
42 module mnit_injected_input
43
44 import mnit_app
45
46 # Concrete event objects that are manually instantiated.
47 # Most InputEvent are extern classes and specific to one platform.
48 #
49 # However, subclasses of this `DummyInputEvent` are genuine Nit
50 # classes and can be instantiated, and more easily manipulated by the programmer.
51 interface DummyInputEvent
52 super InputEvent
53 end
54
55 # A concrete QuitEvent
56 class DummyQuitEvent
57 super DummyInputEvent
58 super QuitEvent
59 redef fun to_s do return "quit"
60 end
61
62 # A concrete PointerEvent
63 class DummyPointerEvent
64 super DummyInputEvent
65 super PointerEvent
66 redef var x: Float
67 redef var y: Float
68 redef fun pressed do return true
69 redef fun to_s do return "click {x} {y}"
70 end
71
72 redef class App
73 # The stream where injected inputs are read
74 private var injected_input_stream: nullable IStream = null
75
76 redef fun setup
77 do
78 var env = "MNIT_SRAND".environ
79 if env != "" then
80 srand_from(env.to_i)
81 end
82
83 var input = "MNIT_READ_INPUT".environ
84 if input != "" then
85 injected_input_stream = new IFStream.open(input)
86 print "GET injected_input_stream {input}"
87 end
88
89 super
90 end
91
92 # Number of frames before the next injected input
93 private var wait_next_input = 0
94
95 # What is the input to inject when `wait_next_input` become 0
96 private var next_input: nullable DummyInputEvent
97
98 redef fun full_frame
99 do
100 if injected_input_stream != null then generate_injected_input
101 super
102 end
103
104 # Internal method to generate injected input events
105 # Is called before each frame
106 # Return `true` is an input event was injexted
107 fun generate_injected_input: Bool
108 do
109 var res = false
110 loop
111 if wait_next_input > 0 then
112 wait_next_input -= 1
113 return res
114 end
115 var n = next_input
116 if n != null then
117 print "INPUT {n}"
118 res = true
119 input(n)
120 end
121 var l = injected_input_stream.read_line
122 if l == "" then
123 print "END OF INPUTS"
124 injected_input_stream.close
125 injected_input_stream = null
126 input(new DummyQuitEvent)
127 return true
128 end
129 print "read {l}"
130 var fs = l.split(" ")
131 if fs.length < 2 then
132 print "BAD EVENT SPEC {l}"
133 res = true
134 input(new DummyQuitEvent)
135 end
136 wait_next_input = fs[0].to_i
137 if fs[1] == "click" then
138 next_input = new DummyPointerEvent(fs[2].to_f, fs[3].to_f)
139 else if fs[1] == "quit" then
140 next_input = new DummyQuitEvent
141 else
142 print "UNKNOWN EVENT {fs[1]} (on {l})"
143 res = true
144 input(new DummyQuitEvent)
145 return true
146 end
147 print "WAIT {wait_next_input} for {next_input.to_s}"
148 end
149 end
150 end