Skip to content

Instantly share code, notes, and snippets.

@z64
Last active June 20, 2023 14:11
Show Gist options
  • Select an option

  • Save z64/27314870d54ea6e606c07c17876b01d8 to your computer and use it in GitHub Desktop.

Select an option

Save z64/27314870d54ea6e606c07c17876b01d8 to your computer and use it in GitHub Desktop.
package demo
import "core:os"
import "core:slice"
import "core:log"
import "core:fmt"
import zd "../0d"
// Set of datum types that this program works with.
Datum :: union {
Bang,
string,
[]byte,
os.Errno,
}
// Zero-sized type used to just kick something off.
Bang :: struct{}
BANG :: Bang{}
// Imports for brevity.
System :: zd.System(Datum)
Component :: zd.Component(Datum)
Connector :: zd.Connector(Datum)
add_component :: zd.add_component
add_connection :: zd.add_connection
run :: zd.run
Port :: zd.Port
send :: zd.send
tran :: zd.tran
ENTER :: zd.ENTER
EXIT :: zd.EXIT
open_file_for_writing :: proc(path: string) -> (os.Handle, os.Errno) {
return os.open(path, os.O_CREATE | os.O_WRONLY | os.O_TRUNC, 0o644)
}
File_Writer_State :: struct {
path: string,
handle: os.Handle,
}
// Initial file writer state.
file_writer_init :: proc(eh: ^Component, port: Port, datum: Datum) {
state := (^File_Writer_State)(eh.data)
switch port {
case ENTER:
eh.data = new(File_Writer_State)
case "open":
path := datum.(string)
handle, err := open_file_for_writing(path)
if err == os.ERROR_NONE {
state.path = path
state.handle = handle
send(eh, "open", BANG)
tran(eh, file_writer_write)
} else {
send(eh, "error", err)
}
}
}
// File opened for writing.
file_writer_write :: proc(eh: ^Component, port: Port, datum: Datum) {
state := (^File_Writer_State)(eh.data)
switch port {
case "write":
bytes := datum.([]byte)
_, err := os.write(state.handle, bytes)
if err == os.ERROR_NONE {
send(eh, "ok", BANG)
} else {
send(eh, "error", err)
}
case EXIT:
os.close(state.handle)
free(eh.data)
}
}
// Reads from stdin.
terminal_input_reader :: proc(eh: ^Component, port: Port, datum: Datum) {
BUFFER_SIZE :: 1024
read_buffer := slice.from_ptr((^u8)(eh.data), BUFFER_SIZE)
switch port {
case ENTER:
read_buffer = make([]u8, BUFFER_SIZE)
eh.data = raw_data(read_buffer)
case "read":
fmt.print("> ")
len, _ := os.read(os.stdin, read_buffer)
if len == 1 && read_buffer[0] == '\n' {
send(eh, "empty", BANG)
} else {
send(eh, "line", read_buffer[:len])
}
case EXIT:
delete(read_buffer)
}
}
// Logs errors.
error_logger :: proc(eh: ^Component, port: Port, datum: Datum) {
switch port {
case ENTER, EXIT:
// ignore
case:
log.errorf("%s: %v", port, datum)
}
}
main :: proc() {
context.logger = log.create_console_logger(
lowest=.Debug,
opt={.Level, .Time, .Terminal_Color},
)
sys: System
file_writer := add_component(&sys, "file_writer", file_writer_init)
terminal_input := add_component(&sys, "terminal_input", terminal_input_reader)
error_logger := add_component(&sys, "error_logger", error_logger)
// Start the network by opening the file.
add_connection(&sys, Connector{
nil, "input",
file_writer, "open",
})
// Once the file is open, read a line.
add_connection(&sys, Connector{
file_writer, "open",
terminal_input, "read",
})
// When a line is produced, write it to the file.
add_connection(&sys, Connector{
terminal_input, "line",
file_writer, "write",
})
// If the write succeeded, read another line.
add_connection(&sys, Connector{
file_writer, "ok",
terminal_input, "read",
})
// Error routing.
add_connection(&sys, Connector{
file_writer, "error",
error_logger, "file writer",
})
file := slice.get(os.args, 1) or_else "demo.txt"
fmt.println("Writing to", file)
fmt.println("Enter empty line to exit.")
run(&sys, "input", file)
}
package zd
import "core:container/queue"
Message :: struct($User_Datum: typeid) {
port: Port,
datum: User_Datum,
}
Port :: distinct string
System :: struct($User_Datum: typeid) {
components: [dynamic]^Component(User_Datum),
connectors: [dynamic]Connector(User_Datum),
}
Connector :: struct($User_Datum: typeid) {
src: ^Component(User_Datum),
src_port: Port,
dst: ^Component(User_Datum),
dst_port: Port,
}
FIFO :: queue.Queue(Message)
fifo_push :: queue.push_back
fifo_pop :: queue.pop_front_safe
Component :: struct($User_Datum: typeid) {
name: string,
input: queue.Queue(Message(User_Datum)),
output: queue.Queue(Message(User_Datum)),
state: #type proc(^Component(User_Datum), Port, User_Datum),
data: rawptr,
}
step :: proc(sys: ^System($User_Datum)) -> (retry: bool) {
for component in sys.components {
for component.output.len > 0 {
msg, _ := fifo_pop(&component.output)
route(sys, component, msg)
}
}
for component in sys.components {
msg, ok := fifo_pop(&component.input)
if ok {
component.state(component, msg.port, msg.datum)
retry = true
}
}
return retry
}
route :: proc(sys: ^System($User_Datum), from: ^Component(User_Datum), msg: Message(User_Datum)) {
for c in sys.connectors {
if c.src == from && c.src_port == msg.port {
new_msg := msg
new_msg.port = c.dst_port
fifo_push(&c.dst.input, new_msg)
}
}
}
run :: proc(sys: ^System($User_Datum), port: Port, data: User_Datum) {
msg := Message(User_Datum){port, data}
route(sys, nil, msg)
for component in sys.components {
component.state(component, ENTER, nil)
}
for step(sys) {
// ...
}
for component in sys.components {
component.state(component, EXIT, nil)
}
}
add_component :: proc(sys: ^System($User_Datum), name: string, handler: proc(^Component(User_Datum), Port, User_Datum)) -> ^Component(User_Datum) {
component := new(Component(User_Datum))
component.name = name
component.state = handler
append(&sys.components, component)
return component
}
add_connection :: proc(sys: ^System($User_Datum), connection: Connector(User_Datum)) {
append(&sys.connectors, connection)
}
send :: proc(component: ^Component($User_Datum), port: Port, data: User_Datum) {
fifo_push(&component.output, Message(User_Datum){port, data})
}
ENTER :: Port("__STATE_ENTER__")
EXIT :: Port("__STATE_EXIT__")
tran :: proc(component: ^Component($User_Datum), state: proc(^Component(User_Datum), Port, User_Datum)) {
component.state(component, EXIT, nil)
component.state = state
component.state(component, ENTER, nil)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment