diff --git a/examples/timer/go_bindings/.gitignore b/examples/timer/go_bindings/.gitignore new file mode 100644 index 0000000..7b332f6 --- /dev/null +++ b/examples/timer/go_bindings/.gitignore @@ -0,0 +1,3 @@ +/libmy_timer.dylib +/libmy_timer.so +/example/example diff --git a/examples/timer/go_bindings/Makefile b/examples/timer/go_bindings/Makefile new file mode 100644 index 0000000..3794498 --- /dev/null +++ b/examples/timer/go_bindings/Makefile @@ -0,0 +1,34 @@ +# Build the Nim dylib next to the generated Go package and run the example. +# +# make run # build libmy_timer + run the example +# make clean +# +# The generated package's cgo directives use ${SRCDIR}, so the library only has +# to sit in this directory (-L/-rpath point here). It is compiled from the repo +# root so the vendored Nimble dependencies resolve. + +REPO_ROOT := $(abspath ../../..) +NIM_SRC := $(REPO_ROOT)/examples/timer/timer.nim + +UNAME_S := $(shell uname -s) +ifeq ($(UNAME_S),Darwin) + LIBNAME := libmy_timer.dylib +else + LIBNAME := libmy_timer.so +endif + +NIMFLAGS := --mm:orc -d:chronicles_log_level=WARN --app:lib --noMain \ + --nimMainPrefix:libmy_timer + +.PHONY: all run clean + +all: $(LIBNAME) + +$(LIBNAME): + cd $(REPO_ROOT) && nim c $(NIMFLAGS) -o:$(CURDIR)/$(LIBNAME) $(NIM_SRC) + +run: $(LIBNAME) + cd example && go run . + +clean: + rm -f $(LIBNAME) example/example diff --git a/examples/timer/go_bindings/README.md b/examples/timer/go_bindings/README.md new file mode 100644 index 0000000..b93ecfc --- /dev/null +++ b/examples/timer/go_bindings/README.md @@ -0,0 +1,58 @@ +# Go (cgo) bindings — native (same-process) example + +Generated cgo bindings for the timer library. The Go wrapper links the library +directly and calls the **native** C ABI, marshalling each `{.ffi.}` type from an +idiomatic Go struct into its flat C-POD form per call. + +## Files + +| File | Description | +|------|-------------| +| `my_timer.go` | Generated cgo package. One Go struct per `{.ffi.}` type plus a `toC()` marshaller; one method per `{.ffi.}` proc. | +| `my_timer.h` | Native C header (emitted alongside the `.go` so cgo's `#include` resolves locally). | +| `go.mod` | Makes the package an importable module. | +| `example/` | A runnable `main.go` that exercises the struct-param methods. | + +Regenerate with `nimble genbindings_go` (from the repo root); the files here are +overwritten each time and `gofmt`-finalized. + +## Mapping + +| Nim (`{.ffi.}`) | Go | +|-----------------|-----| +| `string` | `string` | +| `int` / `int64` … | `int64` … | +| `bool` | `bool` | +| `seq[T]` | `[]T` | +| `Option[T]` / `Maybe[T]` | `*T` (nil = none) | +| nested `{.ffi.}` struct | nested Go struct | + +Each call deep-copies its arguments across the FFI thread, so the Go-side C +allocations are freed (via `defer`) as soon as the call returns. String-returning +methods give back a Go `string`; struct-returning methods deliver their CBOR +encoding (decode it with a Go CBOR library if needed). + +## Build & run + +```sh +cd examples/timer/go_bindings +make run +``` + +Expected output: + +``` +created timer +version: nim-timer v0.1.0 +echo: ok (struct param round-tripped) +complex: ok (seq/option graph deep-copied) +schedule: ok (three struct params in one call) +done +``` + +`Echo`, `Complex` and `Schedule` take `{.ffi.}` structs, slices and optionals +directly — previously these procs were skipped by the Go generator. + +For the cross-process / cross-machine path (CBOR over a socket), see +[`../ipc`](../ipc); a Go client could speak the same wire protocol using any Go +CBOR library. diff --git a/examples/timer/go_bindings/example/go.mod b/examples/timer/go_bindings/example/go.mod new file mode 100644 index 0000000..8e4e65a --- /dev/null +++ b/examples/timer/go_bindings/example/go.mod @@ -0,0 +1,7 @@ +module example + +go 1.21 + +require my_timer v0.0.0 + +replace my_timer => ../ diff --git a/examples/timer/go_bindings/example/main.go b/examples/timer/go_bindings/example/main.go new file mode 100644 index 0000000..4d07673 --- /dev/null +++ b/examples/timer/go_bindings/example/main.go @@ -0,0 +1,70 @@ +// Native (same-process) Go example for the timer library. +// +// The generated cgo package marshals each {.ffi.} struct param into its flat +// C-POD form and passes it to the native ABI by value — so methods that take +// structs, sequences and optionals (Echo, Complex, Schedule) are now callable +// directly with idiomatic Go types. String-returning calls (Version) come back +// as a Go string; struct-returning calls deliver their CBOR encoding. +package main + +import ( + "fmt" + "log" + + timer "my_timer" +) + +func strp(s string) *string { return &s } +func intp(i int64) *int64 { return &i } + +func main() { + // Construct with a TimerConfig struct. + node, err := timer.NewMy_timer(timer.TimerConfig{Name: "go-native-demo"}) + if err != nil { + log.Fatalf("create: %v", err) + } + defer node.Destroy() + fmt.Println("created timer") + + // String return. + if v, err := node.Version(); err == nil { + fmt.Printf("version: %s\n", v) + } + + // Struct param: EchoRequest { Message string; DelayMs int64 }. + if _, err := node.Echo(timer.EchoRequest{Message: "hello from Go", DelayMs: 5}); err != nil { + log.Printf("echo: %v", err) + } else { + fmt.Println("echo: ok (struct param round-tripped)") + } + + // Deeply nested: slice of structs, slice of strings, two optionals. + _, err = node.Complex(timer.ComplexRequest{ + Messages: []timer.EchoRequest{ + {Message: "one", DelayMs: 0}, + {Message: "two", DelayMs: 0}, + }, + Tags: []string{"a", "b"}, + Note: strp("a note"), + Retries: intp(3), + }) + if err != nil { + log.Printf("complex: %v", err) + } else { + fmt.Println("complex: ok (seq/option graph deep-copied)") + } + + // Multiple struct params at once. + _, err = node.Schedule( + timer.JobSpec{Name: "nightly", Payload: []string{"x"}, Priority: 5}, + timer.RetryPolicy{MaxAttempts: 3, BackoffMs: 100, RetryOn: []string{"timeout"}}, + timer.ScheduleConfig{StartAtMs: 1000, IntervalMs: 5000, Jitter: intp(50)}, + ) + if err != nil { + log.Printf("schedule: %v", err) + } else { + fmt.Println("schedule: ok (three struct params in one call)") + } + + fmt.Println("done") +}