nandi/vflutter_ffipublic Fork 0
472eb493bac94bdcb79a38829ccca0c541256fef
Commits
Clone
git clone https://git.rickub.com/nandi/vflutter_ffi.git
git clone ssh://git@rickub.com/nandi/vflutter_ffi.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Replace V with Nim 472eb49 · on 472eb493bac94bdcb79a38829ccca0c541256fef · nandithebull · 4h ago
nimflutter.nim · 113 lines · 3.9 KBNim Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
## C-ABI surface consumed by Dart FFI.
##
## Rules for everything below:
##   * only C-compatible types cross the boundary (cint, cdouble, cstring,
##     pointer) — never a Nim string, seq, ref or object
##   * anything Nim allocates and hands out is released by nf_free
##   * allocations that cross the boundary use the *shared* heap, because Dart
##     calls in from many isolates and each one is its own OS thread

import std/math

var initialized {.global.} = false

proc NimMain() {.importc.}

proc nf_init() {.exportc: "nf_init", dynlib, cdecl.} =
  ## Initialises the Nim runtime. Idempotent and cheap.
  ##
  ## Shared builds run NimMain from a library constructor on ELF/Mach-O, but
  ## static archives (iOS) have no such hook, so the caller needs a guaranteed
  ## entry point.
  if not initialized:
    NimMain()
    initialized = true

proc nf_add(a, b: cint): cint {.exportc: "nf_add", dynlib, cdecl.} =
  a + b

proc nf_greet(name: cstring): cstring {.exportc: "nf_greet", dynlib, cdecl.} =
  ## Returns a shared-heap C string. Caller releases it with nf_free.
  let greeting = "Hello, " & $name & ", from Nim!"
  # allocShared0, not alloc0: the Dart side may free this from a different
  # isolate than the one that allocated it, and Nim's default heap is
  # thread-local.
  let buf = cast[cstring](allocShared0(greeting.len + 1))
  copyMem(buf, greeting.cstring, greeting.len)
  buf

proc nf_free(p: pointer) {.exportc: "nf_free", dynlib, cdecl.} =
  if p != nil:
    deallocShared(p)

proc nf_mandelbrot(buf: ptr UncheckedArray[uint8], bufLen: csize_t,
                   w, h: cint, cx, cy, scale: cdouble, maxIter, y0, y1: cint)
                  {.exportc: "nf_mandelbrot", dynlib, cdecl.} =
  ## Fills rows [y0, y1) of a w x h image as RGBA8888, packed from the start
  ## of `buf`.
  ##
  ## The buffer belongs to the *caller*: nothing is allocated here, so there is
  ## nothing to nf_free, and each call touches only its own rows — which is
  ## what makes concurrent calls from several isolates safe.
  ##
  ## `bufLen` is the caller's own statement of how many bytes `buf` holds, and
  ## every rejection below is all-or-nothing: a short buffer writes *nothing*
  ## rather than filling what fits. Partial output would be indistinguishable
  ## from a rendered frame.
  if buf == nil or w <= 0 or h <= 0 or maxIter <= 0 or y0 < 0 or y1 <= y0:
    return

  # Widen before multiplying. The required size is computed in 64-bit because
  # (y1 - y0) * w * 4 overflows int32 for perfectly ordinary-looking geometry
  # — w = 2^30 wraps to exactly 0, which a length check alone would accept.
  let needed = (y1 - y0).int64 * w.int64 * 4'i64
  if needed <= 0 or needed > bufLen.int64:
    return

  let
    aspect = w.float64 / h.float64
    invW = 1.0 / w.float64
    invH = 1.0 / h.float64

  for y in y0 ..< y1:
    let
      im = cy + (y.float64 * invH - 0.5) * scale
      row = (y - y0) * w * 4
    for x in 0 ..< w:
      let re = cx + (x.float64 * invW - 0.5) * scale * aspect

      var
        zr = 0.0
        zi = 0.0
        zr2 = 0.0
        zi2 = 0.0
        i: cint = 0
      while i < maxIter:
        zr2 = zr * zr
        zi2 = zi * zi
        if zr2 + zi2 > 4.0:
          break
        zi = 2.0 * zr * zi + im
        zr = zr2 - zi2 + re
        inc i

      let idx = row + x * 4
      if i >= maxIter:
        # Inside the set.
        buf[idx] = 0'u8
        buf[idx + 1] = 0'u8
        buf[idx + 2] = 0'u8
        buf[idx + 3] = 255'u8
        continue

      # Smooth (fractional) escape count, so bands don't posterise.
      let mag = sqrt(zr2 + zi2)
      var nu = i.float64
      if mag > 1.0:
        nu = i.float64 + 1.0 - ln(ln(mag) / ln(2.0)) / ln(2.0)
      let t = nu / maxIter.float64

      buf[idx] = uint8(255.0 * (0.5 + 0.5 * sin(3.0 + t * 18.0)))
      buf[idx + 1] = uint8(255.0 * (0.5 + 0.5 * sin(3.6 + t * 18.0)))
      buf[idx + 2] = uint8(255.0 * (0.5 + 0.5 * sin(4.2 + t * 18.0)))
      buf[idx + 3] = 255'u8