Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow polling for input, correctly #118

Merged
merged 2 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions .github/build
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ fi
#
# We build on only a single platform/arch.
#
BUILD_PLATFORMS="linux darwin freebsd windows"
BUILD_PLATFORMS="linux darwin freebsd openbsd netbsd"
BUILD_ARCHS="amd64 386"

# For each platform
Expand All @@ -33,19 +33,13 @@ for OS in ${BUILD_PLATFORMS[@]}; do
SUFFIX="${SUFFIX}-${ARCH}"
fi

# Windows binaries should end in .EXE
if [ "$OS" = "windows" ]; then
SUFFIX="${SUFFIX}.exe"
fi

echo "Building for ${OS} [${ARCH}] -> ${BASE}-${SUFFIX}"

# Run the build
export GOARCH=${ARCH}
export GOOS=${OS}
export CGO_ENABLED=0


go build -ldflags "-X github.com/skx/cpmulator/version.version=$(git describe --tags)" -o "${BASE}-${SUFFIX}"

done
Expand Down
25 changes: 25 additions & 0 deletions consolein/consolein.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,31 @@ func (ci *ConsoleIn) GetInterruptCount() int {
return ci.InterruptCount
}

// PendingInput returns true if there is pending input from STDIN..
//
// Note that we have to set RAW mode, without this input is laggy
// and zork doesn't run.
func (ci *ConsoleIn) PendingInput() bool {

// switch stdin into 'raw' mode
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
return false
}

// Platform-specific code in select_XXXX.go
res := canSelect()

// restore the state of the terminal to avoid mixing RAW/Cooked
err = term.Restore(int(os.Stdin.Fd()), oldState)
if err != nil {
return false
}

// Return true if we have something ready to read.
return res
}

// BlockForCharacterNoEcho returns the next character from the console, blocking until
// one is available.
//
Expand Down
26 changes: 26 additions & 0 deletions consolein/select_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//go:build darwin

package consolein

import (
"os"
"syscall"
)

// canSelect contains a platform-specific implementation of code that tries to use
// SELECT to read from STDIN.
func canSelect() bool {

var readfds syscall.FdSet

fd := os.Stdin.Fd()
readfds.Bits[fd/64] |= 1 << (fd % 64)

// See if input is pending, for a while.
err := syscall.Select(1, &readfds, nil, nil, &syscall.Timeval{Usec: 200})
if err != nil {
return false
}

return true
}
38 changes: 38 additions & 0 deletions consolein/select_freebsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//go:build freebsd

package consolein

import (
"os"
"syscall"
)

// fdget returns index and offset of fd in fds.
func fdget(fd int, fds *syscall.FdSet) (index, offset int) {
index = fd / (syscall.FD_SETSIZE / len(fds.X__fds_bits)) % len(fds.X__fds_bits)
offset = fd % (syscall.FD_SETSIZE / len(fds.X__fds_bits))
return
}

// fdset implements FD_SET macro.
func fdset(fd int, fds *syscall.FdSet) {
idx, pos := fdget(fd, fds)
fds.X__fds_bits[idx] = 1 << uint(pos)
}

// canSelect contains a platform-specific implementation of code that tries to use
// SELECT to read from STDIN.
func canSelect() bool {

var readfds syscall.FdSet

fdset(int(os.Stdin.Fd()), &readfds)

// See if input is pending, for a while.
err := syscall.Select(1, &readfds, nil, nil, &syscall.Timeval{Usec: 200})
if err != nil {
return false
}

return true
}
26 changes: 26 additions & 0 deletions consolein/select_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//go:build linux

package consolein

import (
"os"
"syscall"
)

// canSelect contains a platform-specific implementation of code that tries to use
// SELECT to read from STDIN.
func canSelect() bool {

var readfds syscall.FdSet

fd := os.Stdin.Fd()
readfds.Bits[fd/64] |= 1 << (fd % 64)

// See if input is pending, for a while.
nRead, err := syscall.Select(1, &readfds, nil, nil, &syscall.Timeval{Usec: 200})
if err != nil {
return false
}

return (nRead > 0)
}
26 changes: 26 additions & 0 deletions consolein/select_netbsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//go:build netbsd

package consolein

import (
"os"
"syscall"
)

// canSelect contains a platform-specific implementation of code that tries to use
// SELECT to read from STDIN.
func canSelect() bool {

var readfds syscall.FdSet

fd := os.Stdin.Fd()
readfds.Bits[fd/64] |= 1 << (fd % 64)

// See if input is pending, for a while.
err := syscall.Select(1, &readfds, nil, nil, &syscall.Timeval{Usec: 200})
if err != nil {
return false
}

return true
}
26 changes: 26 additions & 0 deletions consolein/select_openbsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//go:build openbsd

package consolein

import (
"os"
"syscall"
)

// canSelect contains a platform-specific implementation of code that tries to use
// SELECT to read from STDIN.
func canSelect() bool {

var readfds syscall.FdSet

fd := os.Stdin.Fd()
readfds.Bits[fd/64] |= 1 << (fd % 64)

// See if input is pending, for a while.
err := syscall.Select(1, &readfds, nil, nil, &syscall.Timeval{Usec: 200})
if err != nil {
return false
}

return true
}
2 changes: 0 additions & 2 deletions cpm/cpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,6 @@ func New(logger *slog.Logger, prn string, condriver string, ccp string) (*CPM, e
sys[11] = CPMHandler{
Desc: "C_STAT",
Handler: SysCallConsoleStatus,
Fake: true,
}
sys[12] = CPMHandler{
Desc: "S_BDOSVER",
Expand Down Expand Up @@ -378,7 +377,6 @@ func New(logger *slog.Logger, prn string, condriver string, ccp string) (*CPM, e
b[2] = CPMHandler{
Desc: "CONST",
Handler: BiosSysCallConsoleStatus,
Fake: true,
}
b[3] = CPMHandler{
Desc: "CONIN",
Expand Down
51 changes: 48 additions & 3 deletions cpm/cpm_bdos.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,18 +99,58 @@ func SysCallAuxWrite(cpm *CPM) error {
}

// SysCallRawIO handles both simple character output, and input.
//
// Note that we have to poll and determine if character input is present
// in this function, otherwise games and things don't work well without it.
//
// Blocking in the handler for 0xFF will make ZORK X work, but not other things
// this is the single hardest function to work with. Meh.
func SysCallRawIO(cpm *CPM) error {

switch cpm.CPU.States.DE.Lo {
case 0xFF, 0xFD:
case 0xFF:
// Return a character without echoing if one is waiting; zero if none is available.
if cpm.input.PendingInput() {
out, err := cpm.input.BlockForCharacterNoEcho()
if err != nil {
return err
}
cpm.CPU.States.AF.Hi = out
cpm.CPU.States.HL.Lo = out
cpm.CPU.States.AF.Lo = 0x00
return nil
}
// nothing pending, return 0x00
cpm.CPU.States.AF.Hi = 0x00
cpm.CPU.States.AF.Lo = 0x00
cpm.CPU.States.HL.Lo = 0x00
return nil
case 0xFE:
// Return console input status. Zero if no character is waiting, nonzero otherwise.
if cpm.input.PendingInput() {

cpm.CPU.States.AF.Hi = 0xFF
cpm.CPU.States.AF.Lo = 0x00
cpm.CPU.States.HL.Lo = 0xFF
} else {
cpm.CPU.States.AF.Hi = 0x00
cpm.CPU.States.AF.Lo = 0x00
cpm.CPU.States.HL.Lo = 0x00

}
return nil
case 0xFD:
// Wait until a character is ready, return it without echoing.
out, err := cpm.input.BlockForCharacterNoEcho()
if err != nil {
return err
}
cpm.CPU.States.AF.Hi = out
cpm.CPU.States.AF.Lo = 0x00
cpm.CPU.States.HL.Lo = 0x00
return nil
default:
// Anything else is to output a character.
cpm.output.PutCharacter(cpm.CPU.States.DE.Lo)
}
return nil
Expand Down Expand Up @@ -216,8 +256,13 @@ func SysCallReadString(cpm *CPM) error {
// SysCallConsoleStatus tests if we have pending console (character) input.
func SysCallConsoleStatus(cpm *CPM) error {

// Nothing pending
cpm.CPU.States.AF.Hi = 0x00
if cpm.input.PendingInput() {
cpm.CPU.States.AF.Hi = 0xFF
cpm.CPU.States.HL.Lo = 0xFF
} else {
cpm.CPU.States.AF.Hi = 0x00
cpm.CPU.States.HL.Lo = 0x00
}
return nil
}

Expand Down
9 changes: 7 additions & 2 deletions cpm/cpm_bios.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,14 @@ func BiosSysCallBoot(cpm *CPM) error {
}

// BiosSysCallConsoleStatus should return 0x00 if there is no input
// pending, otherwise 0xFF. We fake it
// pending, otherwise 0xFF.
func BiosSysCallConsoleStatus(cpm *CPM) error {
cpm.CPU.States.AF.Hi = 0x00

if cpm.input.PendingInput() {
cpm.CPU.States.AF.Hi = 0xFF
} else {
cpm.CPU.States.AF.Hi = 0x00
}

return nil
}
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.22.2

require (
github.com/koron-go/z80 v0.10.0
golang.org/x/term v0.20.0
golang.org/x/term v0.21.0
)

require golang.org/x/sys v0.20.0
require golang.org/x/sys v0.21.0 // indirect
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/koron-go/z80 v0.10.0 h1:eYyL1Lj90sDb1JLkOlj9hwZWKrhWyqVraj4+d0dQszs=
github.com/koron-go/z80 v0.10.0/go.mod h1:ry+Zl9kRKelzaDG9UzEtUpUnXy0Yv/kk1YEaX958xdk=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
Loading