-
-
Save z64/27314870d54ea6e606c07c17876b01d8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
