Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 215 lines (201 sloc) 5.128 kb
f29cd5b @kballard Implement dcpu.Machine
authored
1 package dcpu
2
3 import (
4 "errors"
d1e4fc8 @kballard Tweaks to dcpu/machine
authored
5 "fmt"
39fa20f @kballard Add a function machine.EffectiveClockRate()
authored
6 "github.com/kballard/dcpu16/dcpu/core"
78ec5b5 @kballard Add flags to control the clock rate
authored
7 "io"
825b03f @kballard Tweak clock rate string printing
authored
8 "strconv"
78ec5b5 @kballard Add flags to control the clock rate
authored
9 "strings"
f29cd5b @kballard Implement dcpu.Machine
authored
10 "time"
11 )
12
13 type Machine struct {
39fa20f @kballard Add a function machine.EffectiveClockRate()
authored
14 State core.State
15 Video Video
e289004 @kballard Implement the keyboard
authored
16 Keyboard Keyboard
39fa20f @kballard Add a function machine.EffectiveClockRate()
authored
17 stopper chan<- struct{}
18 stopped <-chan error
19 cycleCount uint
20 startTime time.Time
f29cd5b @kballard Implement dcpu.Machine
authored
21 }
22
d1e4fc8 @kballard Tweaks to dcpu/machine
authored
23 type MachineError struct {
24 UnderlyingError error
25 PC core.Word
26 }
27
28 func (err *MachineError) Error() string {
29 return fmt.Sprintf("machine error occurred; PC: %#x (%v)", err.PC, err.UnderlyingError)
30 }
31
78ec5b5 @kballard Add flags to control the clock rate
authored
32 const DefaultClockRate ClockRate = 100000 // 100KHz
5da30d1 @kballard Define dcpu.DefaultClockRate
authored
33
f29cd5b @kballard Implement dcpu.Machine
authored
34 // Start boots up the machine, with a clock rate of 1 / period
35 // 10MHz would be expressed as (Microsecond / 10)
e289004 @kballard Implement the keyboard
authored
36 func (m *Machine) Start(rate ClockRate) (err error) {
3603d6b @kballard Rewrite the core to remove the goroutine stuff
authored
37 if m.stopped != nil {
38 return errors.New("Machine has already started")
39 }
e289004 @kballard Implement the keyboard
authored
40 if err = m.Video.Init(); err != nil {
41 return
5f0f122 @kballard Implement a video display
authored
42 }
e289004 @kballard Implement the keyboard
authored
43 defer func() {
44 if err != nil {
45 m.Video.Close()
46 }
47 }()
48 if err = m.Video.MapToMachine(0x8000, m); err != nil {
49 return
50 }
51 if err = m.Keyboard.MapToMachine(0x9000, m); err != nil {
52 return
5f0f122 @kballard Implement a video display
authored
53 }
d1e4fc8 @kballard Tweaks to dcpu/machine
authored
54 stopper := make(chan struct{}, 1)
f29cd5b @kballard Implement dcpu.Machine
authored
55 m.stopper = stopper
d1e4fc8 @kballard Tweaks to dcpu/machine
authored
56 stopped := make(chan error, 1)
f29cd5b @kballard Implement dcpu.Machine
authored
57 m.stopped = stopped
39fa20f @kballard Add a function machine.EffectiveClockRate()
authored
58 m.cycleCount = 0
59 m.startTime = time.Now()
f29cd5b @kballard Implement dcpu.Machine
authored
60 go func() {
9c78645 @kballard Add a ticker buffer
authored
61 // we want an acurate cycle counter
62 // Unfortunately, time.NewTicker drops cycles on the floor if it can't keep up
b8522b8 @kballard Rewrite the cycle driver
authored
63 // So lets instead switch to running as many cycles as we need before using any
64 // timed delays
65 cycleChan := make(chan time.Time, 1)
59f1b3b @kballard Add a flag to control the screen refresh rate
authored
66 refreshRate := m.Video.RefreshRate
67 if refreshRate == 0 {
68 refreshRate = DefaultScreenRefreshRate
69 }
70 scanrate := time.NewTicker(refreshRate.ToDuration())
d1e4fc8 @kballard Tweaks to dcpu/machine
authored
71 var stoperr error
b8522b8 @kballard Rewrite the cycle driver
authored
72 nextTime := time.Now()
73 period := rate.ToDuration()
74 cycleChan <- nextTime
75 var timerChan <-chan time.Time
76 // runCycle needs to be split into a function, because we want to call it if
77 // any of two channels has a value
78 runCycle := func() bool {
79 if err := m.State.StepCycle(); err != nil {
80 stoperr = &MachineError{err, m.State.PC()}
81 return false
82 }
83 m.cycleCount++
84 m.Keyboard.PollKeys()
85 nextTime = nextTime.Add(period)
86 now := time.Now()
87 if now.Before(nextTime) {
88 // delay the cycle
89 timerChan = time.After(nextTime.Sub(now))
90 } else {
91 // trigger a cycle now
92 cycleChan <- now
93 }
94 return true
95 }
d1e4fc8 @kballard Tweaks to dcpu/machine
authored
96 loop:
f29cd5b @kballard Implement dcpu.Machine
authored
97 for {
98 select {
5f0f122 @kballard Implement a video display
authored
99 case _ = <-scanrate.C:
3c95064 @kballard Display registers below the screen
authored
100 m.Video.UpdateStats(&m.State, m.cycleCount)
5f0f122 @kballard Implement a video display
authored
101 m.Video.Flush()
b8522b8 @kballard Rewrite the cycle driver
authored
102 case _ = <-timerChan:
103 if !runCycle() {
104 break loop
105 }
9c78645 @kballard Add a ticker buffer
authored
106 case _ = <-cycleChan:
b8522b8 @kballard Rewrite the cycle driver
authored
107 if !runCycle() {
d1e4fc8 @kballard Tweaks to dcpu/machine
authored
108 break loop
f29cd5b @kballard Implement dcpu.Machine
authored
109 }
110 case _ = <-stopper:
d1e4fc8 @kballard Tweaks to dcpu/machine
authored
111 break loop
f29cd5b @kballard Implement dcpu.Machine
authored
112 }
113 }
5f0f122 @kballard Implement a video display
authored
114 scanrate.Stop()
d1e4fc8 @kballard Tweaks to dcpu/machine
authored
115 stopped <- stoperr
f29cd5b @kballard Implement dcpu.Machine
authored
116 close(stopped)
117 }()
118 return nil
119 }
120
121 // Stop stops the machine. Returns an error if it's already stopped.
122 // If the machine has halted due to an error, that error is returned.
123 func (m *Machine) Stop() error {
3603d6b @kballard Rewrite the core to remove the goroutine stuff
authored
124 if m.stopped == nil {
125 return errors.New("Machine has not started")
f29cd5b @kballard Implement dcpu.Machine
authored
126 }
e289004 @kballard Implement the keyboard
authored
127 m.Video.UnmapFromMachine(0x8000, m)
128 m.Keyboard.UnmapFromMachine(0x9000, m)
3603d6b @kballard Rewrite the core to remove the goroutine stuff
authored
129 m.stopper <- struct{}{}
ddf8d1f @kballard Close the Video slightly later
authored
130 m.Video.Close()
5f0f122 @kballard Implement a video display
authored
131 err := <-m.stopped
f29cd5b @kballard Implement dcpu.Machine
authored
132 close(m.stopper)
133 m.stopper = nil
134 m.stopped = nil
5f0f122 @kballard Implement a video display
authored
135 return err
f29cd5b @kballard Implement dcpu.Machine
authored
136 }
137
78ec5b5 @kballard Add flags to control the clock rate
authored
138 // ClockRate represents the clock rate of the machine
139 type ClockRate int64
39fa20f @kballard Add a function machine.EffectiveClockRate()
authored
140
78ec5b5 @kballard Add flags to control the clock rate
authored
141 func (c ClockRate) String() string {
825b03f @kballard Tweak clock rate string printing
authored
142 rate := float64(c)
143 // We want to do some rounding instead of pure truncation
144 // 99.999KHz shouldn't be showing as 99KHz
145 // Lets try rounding to 1 decimal place
ebb448a @kballard Add convenience function dcpu.ClockRateToString()
authored
146 suffix := "Hz"
78ec5b5 @kballard Add flags to control the clock rate
authored
147 if rate >= 1e6 {
ebb448a @kballard Add convenience function dcpu.ClockRateToString()
authored
148 rate /= 1e6
78ec5b5 @kballard Add flags to control the clock rate
authored
149 suffix = "MHz"
150 } else if rate >= 1e3 {
ebb448a @kballard Add convenience function dcpu.ClockRateToString()
authored
151 rate /= 1e3
78ec5b5 @kballard Add flags to control the clock rate
authored
152 suffix = "KHz"
ebb448a @kballard Add convenience function dcpu.ClockRateToString()
authored
153 }
825b03f @kballard Tweak clock rate string printing
authored
154 ratestr := strconv.FormatFloat(rate, 'f', 1, 64)
155 if strings.HasSuffix(ratestr, ".0") {
156 ratestr = ratestr[:len(ratestr)-2]
157 }
158 return fmt.Sprintf("%s%s", ratestr, suffix)
ebb448a @kballard Add convenience function dcpu.ClockRateToString()
authored
159 }
160
78ec5b5 @kballard Add flags to control the clock rate
authored
161 func (c *ClockRate) Set(str string) error {
162 var rate int64
163 var suffix string
164 if n, err := fmt.Sscanf(str, "%d%s", &rate, &suffix); err != nil && !(n == 1 && err == io.EOF) {
165 return err
166 }
167 if rate <= 0 {
168 return errors.New("clock rate must be positive")
169 }
170 switch strings.ToLower(suffix) {
171 case "mhz":
172 rate *= 1e6
173 case "khz":
174 rate *= 1e3
175 case "hz", "":
176 default:
177 return errors.New(fmt.Sprintf("unknown suffix %#v", suffix))
178 }
179 *c = ClockRate(rate)
180 return nil
181 }
182
183 // ToDuration converts the ClockRate to a time.Duration that represents
184 // the period of one clock cycle
185 func (c ClockRate) ToDuration() time.Duration {
186 return time.Second / time.Duration(c)
187 }
188
189 // EffectiveClockRate returns the current observed rate that the machine
190 // is running at, as an average since the last Start()
191 func (m *Machine) EffectiveClockRate() ClockRate {
192 duration := time.Since(m.startTime)
193 cycles := m.cycleCount
194 return ClockRate(float64(cycles) / duration.Seconds())
195 }
196
f29cd5b @kballard Implement dcpu.Machine
authored
197 // If the machine has already halted due to an error, that error is returned.
198 // Otherwise, nil is returned.
199 // If the machine has not started, an error is returned.
200 func (m *Machine) HasError() error {
201 if m.stopped == nil {
202 return errors.New("Machine has not started")
203 }
204 select {
205 case err := <-m.stopped:
8bade67 @kballard Don't close the video in the goroutine
authored
206 m.Video.Close()
f29cd5b @kballard Implement dcpu.Machine
authored
207 close(m.stopper)
208 m.stopper = nil
209 m.stopped = nil
210 return err
211 default:
212 }
213 return nil
214 }
Something went wrong with that request. Please try again.