random package can produce numbers faster for the running emulation

This commit is contained in:
JetSetIlly 2022-01-19 18:48:35 +00:00
parent c767b1c1d5
commit 6311063b89
16 changed files with 92 additions and 51 deletions

View file

@ -124,12 +124,12 @@ func (mc *CPU) Reset() {
// checking for instance == nil because it's possible for NewCPU to be
// called with a nil instance (test package)
if mc.instance != nil && mc.instance.Prefs.RandomState.Get().(bool) {
mc.PC.Load(uint16(mc.instance.Random.Intn(0xffff)))
mc.A.Load(uint8(mc.instance.Random.Intn(0xff)))
mc.X.Load(uint8(mc.instance.Random.Intn(0xff)))
mc.Y.Load(uint8(mc.instance.Random.Intn(0xff)))
mc.SP.Load(uint8(mc.instance.Random.Intn(0xff)))
mc.Status.FromValue(uint8(mc.instance.Random.Intn(0xff)))
mc.PC.Load(uint16(mc.instance.Random.NoRewind(0xffff)))
mc.A.Load(uint8(mc.instance.Random.NoRewind(0xff)))
mc.X.Load(uint8(mc.instance.Random.NoRewind(0xff)))
mc.Y.Load(uint8(mc.instance.Random.NoRewind(0xff)))
mc.SP.Load(uint8(mc.instance.Random.NoRewind(0xff)))
mc.Status.FromValue(uint8(mc.instance.Random.NoRewind(0xff)))
} else {
mc.PC.Load(0)
mc.A.Load(0)

View file

@ -84,10 +84,10 @@ func (r Registers) String() string {
func (r *Registers) reset(rand *random.Random) {
for i := range r.Fetcher {
if rand != nil {
r.Fetcher[i].Low = byte(rand.Intn(0xff))
r.Fetcher[i].Hi = byte(rand.Intn(0xff))
r.Fetcher[i].Top = byte(rand.Intn(0xff))
r.Fetcher[i].Bottom = byte(rand.Intn(0xff))
r.Fetcher[i].Low = byte(rand.NoRewind(0xff))
r.Fetcher[i].Hi = byte(rand.NoRewind(0xff))
r.Fetcher[i].Top = byte(rand.NoRewind(0xff))
r.Fetcher[i].Bottom = byte(rand.NoRewind(0xff))
} else {
r.Fetcher[i].Low = 0
r.Fetcher[i].Hi = 0
@ -98,10 +98,10 @@ func (r *Registers) reset(rand *random.Random) {
for i := range r.FracFetcher {
if rand != nil {
r.FracFetcher[i].Low = byte(rand.Intn(0xff))
r.FracFetcher[i].Hi = byte(rand.Intn(0xff))
r.FracFetcher[i].Increment = byte(rand.Intn(0xff))
r.FracFetcher[i].Count = byte(rand.Intn(0xff))
r.FracFetcher[i].Low = byte(rand.NoRewind(0xff))
r.FracFetcher[i].Hi = byte(rand.NoRewind(0xff))
r.FracFetcher[i].Increment = byte(rand.NoRewind(0xff))
r.FracFetcher[i].Count = byte(rand.NoRewind(0xff))
} else {
r.FracFetcher[i].Low = 0
r.FracFetcher[i].Hi = 0
@ -112,9 +112,9 @@ func (r *Registers) reset(rand *random.Random) {
for i := range r.MusicFetcher {
if rand != nil {
r.MusicFetcher[i].Waveform = uint32(rand.Intn(0xffffffff))
r.MusicFetcher[i].Freq = uint32(rand.Intn(0xffffffff))
r.MusicFetcher[i].Count = uint32(rand.Intn(0xffffffff))
r.MusicFetcher[i].Waveform = uint32(rand.NoRewind(0xffffffff))
r.MusicFetcher[i].Freq = uint32(rand.NoRewind(0xffffffff))
r.MusicFetcher[i].Count = uint32(rand.NoRewind(0xffffffff))
} else {
r.MusicFetcher[i].Waveform = 0
r.MusicFetcher[i].Freq = 0
@ -123,7 +123,7 @@ func (r *Registers) reset(rand *random.Random) {
}
if rand != nil {
r.RNG.Value = uint32(rand.Intn(0xffffffff))
r.RNG.Value = uint32(rand.NoRewind(0xffffffff))
} else {
r.RNG.Value = 0
}

View file

@ -103,7 +103,7 @@ func (cart *m3e) Reset() {
for b := range cart.state.ram {
for i := range cart.state.ram[b] {
if cart.instance.Prefs.RandomState.Get().(bool) {
cart.state.ram[b][i] = uint8(cart.instance.Random.Intn(0xff))
cart.state.ram[b][i] = uint8(cart.instance.Random.NoRewind(0xff))
} else {
cart.state.ram[b][i] = 0
}

View file

@ -111,7 +111,7 @@ func (cart *m3ePlus) Reset() {
for b := range cart.state.ram {
for i := range cart.state.ram[b] {
if cart.instance.Prefs.RandomState.Get().(bool) {
cart.state.ram[b][i] = uint8(cart.instance.Random.Intn(0xff))
cart.state.ram[b][i] = uint8(cart.instance.Random.NoRewind(0xff))
} else {
cart.state.ram[b][i] = 0
}

View file

@ -120,7 +120,7 @@ func (cart *atari) ID() string {
func (cart *atari) Reset() {
for i := range cart.state.ram {
if cart.instance.Prefs.RandomState.Get().(bool) {
cart.state.ram[i] = uint8(cart.instance.Random.Intn(0xff))
cart.state.ram[i] = uint8(cart.instance.Random.NoRewind(0xff))
} else {
cart.state.ram[i] = 0
}

View file

@ -99,7 +99,7 @@ func (cart *cbs) Plumb() {
func (cart *cbs) Reset() {
for i := range cart.state.ram {
if cart.instance.Prefs.RandomState.Get().(bool) {
cart.state.ram[i] = uint8(cart.instance.Random.Intn(0xff))
cart.state.ram[i] = uint8(cart.instance.Random.NoRewind(0xff))
} else {
cart.state.ram[i] = 0
}

View file

@ -87,7 +87,7 @@ func (cart *df) Plumb() {
func (cart *df) Reset() {
for i := range cart.state.ram {
if cart.instance.Prefs.RandomState.Get().(bool) {
cart.state.ram[i] = uint8(cart.instance.Random.Intn(0xff))
cart.state.ram[i] = uint8(cart.instance.Random.NoRewind(0xff))
} else {
cart.state.ram[i] = 0
}

View file

@ -622,10 +622,10 @@ func (r DPCregisters) String() string {
func (r *DPCregisters) reset(rand *random.Random) {
for i := range r.Fetcher {
if rand != nil {
r.Fetcher[i].Low = byte(rand.Intn(0xff))
r.Fetcher[i].Hi = byte(rand.Intn(0xff))
r.Fetcher[i].Top = byte(rand.Intn(0xff))
r.Fetcher[i].Bottom = byte(rand.Intn(0xff))
r.Fetcher[i].Low = byte(rand.NoRewind(0xff))
r.Fetcher[i].Hi = byte(rand.NoRewind(0xff))
r.Fetcher[i].Top = byte(rand.NoRewind(0xff))
r.Fetcher[i].Bottom = byte(rand.NoRewind(0xff))
} else {
r.Fetcher[i].Low = 0
r.Fetcher[i].Hi = 0
@ -640,7 +640,7 @@ func (r *DPCregisters) reset(rand *random.Random) {
}
if rand != nil {
r.RNG = uint8(rand.Intn(0xff))
r.RNG = uint8(rand.NoRewind(0xff))
} else {
r.RNG = 0
}

View file

@ -145,7 +145,7 @@ func (cart *mnetwork) Reset() {
for b := range cart.state.ram256byte {
for i := range cart.state.ram256byte[b] {
if cart.instance.Prefs.RandomState.Get().(bool) {
cart.state.ram256byte[b][i] = uint8(cart.instance.Random.Intn(0xff))
cart.state.ram256byte[b][i] = uint8(cart.instance.Random.NoRewind(0xff))
} else {
cart.state.ram256byte[b][i] = 0
}
@ -154,7 +154,7 @@ func (cart *mnetwork) Reset() {
for i := range cart.state.ram1k {
if cart.instance.Prefs.RandomState.Get().(bool) {
cart.state.ram1k[i] = uint8(cart.instance.Random.Intn(0xff))
cart.state.ram1k[i] = uint8(cart.instance.Random.NoRewind(0xff))
} else {
cart.state.ram1k[i] = 0
}

View file

@ -115,7 +115,7 @@ func (cart *Supercharger) Plumb() {
func (cart *Supercharger) Reset() {
for b := range cart.state.ram {
for i := range cart.state.ram[b] {
cart.state.ram[b][i] = uint8(cart.instance.Random.Intn(0xff))
cart.state.ram[b][i] = uint8(cart.instance.Random.NoRewind(0xff))
}
}

View file

@ -157,7 +157,7 @@ func (mem *Memory) read(address uint16) (uint8, error) {
mem.LastCPUDrivenPins = vcs.TIADrivenPins
data &= vcs.TIADrivenPins
if mem.instance != nil && mem.instance.Prefs.RandomPins.Get().(bool) {
data |= uint8(mem.instance.Random.Intn(0xff)) & ^vcs.TIADrivenPins
data |= uint8(mem.instance.Random.Rewindable(0xff)) & ^vcs.TIADrivenPins
} else {
data |= mem.LastCPUData & ^vcs.TIADrivenPins
}

View file

@ -50,7 +50,7 @@ func (ram *RAM) Snapshot() *RAM {
func (ram *RAM) Reset() {
for i := range ram.RAM {
if ram.instance != nil && ram.instance.Prefs.RandomState.Get().(bool) {
ram.RAM[i] = uint8(ram.instance.Random.Intn(0xff))
ram.RAM[i] = uint8(ram.instance.Random.NoRewind(0xff))
} else {
ram.RAM[i] = 0
}

View file

@ -100,8 +100,8 @@ func (tmr *Timer) Reset() {
if tmr.instance.Prefs.RandomState.Get().(bool) {
tmr.divider = T1024T
tmr.ticksRemaining = tmr.instance.Random.Intn(0xffff)
tmr.mem.ChipWrite(chipbus.INTIM, uint8(tmr.instance.Random.Intn(0xff)))
tmr.ticksRemaining = tmr.instance.Random.NoRewind(0xffff)
tmr.mem.ChipWrite(chipbus.INTIM, uint8(tmr.instance.Random.NoRewind(0xff)))
} else {
tmr.divider = T1024T
tmr.ticksRemaining = int(T1024T)

View file

@ -16,12 +16,20 @@
// Package random should be used in preference to the math/rand package when a
// random number is required inside the emulation.
//
// It ultimately uses the RNG from the standard library but adds an
// understanding of how time is measured inside the emulation and ensures that
// the same random number is generated at the same point in the emulation's
// "timeline".
// There are two functions belonging to the Rewind type that return random
// numbers:
//
// An essential ingredient when generating random numbers in parallel
// emulations, that are meant to be identical; and when rewinding and replaying
// a single emulation.
// Rewindable() returns numbers based on the current television coordinates.
// The number will always return the same number for the same coordinates. As
// such it is compatible with the emulator's rewind system.
//
// NoRewind() returns random numbers regardless of the current television
// coordinates. It is therefore, not compatiable with the emulator's rewind
// system.
//
// Parallel emulators should return the same sequence of random numbers even if
// NoRewind() is used.
//
// If the same random numbers are required every single time then set ZeroSeed
// to true. This is useful for testing purposes.
package random

View file

@ -44,12 +44,16 @@ type Random struct {
// use zero seed rather than the random base seed. this is only really
// useful for normalised instances where random numbers must be predictable
ZeroSeed bool
// standard Go random number generator for NoRewind()
nonRewindable rand.Source
}
// NewRandom is the preferred method of initialisation for the Random type.
func NewRandom(tv TV) *Random {
return &Random{
tv: tv,
tv: tv,
nonRewindable: rand.NewSource(baseSeed),
}
}
@ -58,14 +62,43 @@ func coordsSum(c coords.TelevisionCoords) int64 {
return int64(c.Frame*specification.AbsoluteMaxClks + c.Scanline*specification.ClksScanline + c.Clock)
}
// new RNG from the standard library
func (rnd *Random) rand() *rand.Rand {
if rnd.ZeroSeed {
return rand.New(rand.NewSource(coordsSum(rnd.tv.GetCoords())))
// Rewindable generates a random number very quickly and based on the current
// television coordinates. It's only really suitable for use in a running
// emulation.
//
// It does however have the property of being predictable during a sesssion and
// so is compatible with the rewind system.
//
// The returned number will between zero and value given in the n parameter.
func (rnd *Random) Rewindable(n int) int {
if n == 0 {
return 0
}
return rand.New(rand.NewSource(baseSeed + coordsSum(rnd.tv.GetCoords())))
seed := coordsSum(rnd.tv.GetCoords())
if !rnd.ZeroSeed {
seed += baseSeed
}
seed *= seed
b := seed >> 32
if b != 0 {
seed %= b
}
return int(seed % int64(n))
}
func (rnd *Random) Intn(n int) int {
return rnd.rand().Intn(n)
// NoRewind uses the standard Go library for generating random numbers. It can
// be used to generate random numbers on a non-running emulation but it is not
// therefore compatible with the rewind system.
//
// It is useful for generating a random state on startup.
//
// The returned number will between zero and value given in the n parameter.
func (rnd *Random) NoRewind(n int) int {
if n == 0 {
return 0
}
return int(rnd.nonRewindable.Int63() % int64(n))
}

View file

@ -39,6 +39,6 @@ func TestRandom(t *testing.T) {
b := random.NewRandom(&mockTV{})
for i := 1; i < 256; i++ {
test.Equate(t, a.Intn(i), b.Intn(i))
test.Equate(t, a.Rewindable(i), b.Rewindable(i))
}
}