fix(botapi): static SVG + intent buffers to kill cross-thread heap realloc
Round N+1's fresh bot thread realloc'd module-level strings/seqs (SVG buffer, intent stdout/stderr, team messages) left behind by dead round N's thread — same rawDealloc SIGSEGV class as the event queue, seen at graphics.nim:274 (drawText->prepareAdd, core 2490478 @ 03:13:40, battle round 257). - graphics.nim: gSvgBuffer -> array[16384, char] + gSvgLen, appendSvg - bot.nim: intent stdout/stderr -> static char arrays; team messages -> array[16, TeamMessage] + len; buildIntentJson/printToStdOut/Err/ broadcastTeamMessage bounded appends - botThreadEntry: reset graphics+intent buffers on the owning thread Also fixes stale mapActions call sites in tests/ (missing enemyX/enemyY).
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -20,7 +20,7 @@ proc makeRaw(vals: array[6, float32]): Tensor[float32] =
|
|||||||
# --- 6-dim input produces a valid BotActions ---
|
# --- 6-dim input produces a valid BotActions ---
|
||||||
block basicDecode:
|
block basicDecode:
|
||||||
let raw = makeRaw([0.0'f32, 0.0, 0.0, 0.0, 0.0, 0.0])
|
let raw = makeRaw([0.0'f32, 0.0, 0.0, 0.0, 0.0, 0.0])
|
||||||
let acts = mapActions(raw, 0.0, arenaW, arenaH, 600.0, 400.0, 0.0, 0.0, 0.0)
|
let acts = mapActions(raw, 0.0, arenaW, arenaH, 600.0, 400.0, 0.0, 0.0, 0.0, 600.0, 400.0)
|
||||||
# sigmoid(0)*arenaW = 0.5*1200 = 600, sigmoid(0)*arenaH = 0.5*800 = 400
|
# sigmoid(0)*arenaW = 0.5*1200 = 600, sigmoid(0)*arenaH = 0.5*800 = 400
|
||||||
check abs(acts.gotoX - 600.0) < 1e-6, "gotoX = sigmoid(0)*arenaW"
|
check abs(acts.gotoX - 600.0) < 1e-6, "gotoX = sigmoid(0)*arenaW"
|
||||||
check abs(acts.gotoY - 400.0) < 1e-6, "gotoY = sigmoid(0)*arenaH"
|
check abs(acts.gotoY - 400.0) < 1e-6, "gotoY = sigmoid(0)*arenaH"
|
||||||
@@ -31,13 +31,13 @@ block basicDecode:
|
|||||||
block coordBounds:
|
block coordBounds:
|
||||||
# Large positive raw → sigmoid ≈ 1 → close to arenaW/arenaH
|
# Large positive raw → sigmoid ≈ 1 → close to arenaW/arenaH
|
||||||
let rawHigh = makeRaw([100.0'f32, 100.0, 100.0, 100.0, 0.0, 0.0])
|
let rawHigh = makeRaw([100.0'f32, 100.0, 100.0, 100.0, 0.0, 0.0])
|
||||||
let actsHigh = mapActions(rawHigh, 0.0, arenaW, arenaH, 600.0, 400.0, 0.0, 0.0, 0.0)
|
let actsHigh = mapActions(rawHigh, 0.0, arenaW, arenaH, 600.0, 400.0, 0.0, 0.0, 0.0, 600.0, 400.0)
|
||||||
check actsHigh.gotoX <= arenaW + 1e-9, "gotoX <= arenaWidth"
|
check actsHigh.gotoX <= arenaW + 1e-9, "gotoX <= arenaWidth"
|
||||||
check actsHigh.gotoY <= arenaH + 1e-9, "gotoY <= arenaHeight"
|
check actsHigh.gotoY <= arenaH + 1e-9, "gotoY <= arenaHeight"
|
||||||
check actsHigh.gotoX >= 0.0, "gotoX >= 0"
|
check actsHigh.gotoX >= 0.0, "gotoX >= 0"
|
||||||
# Large negative raw → sigmoid ≈ 0 → close to 0
|
# Large negative raw → sigmoid ≈ 0 → close to 0
|
||||||
let rawLow = makeRaw([-100.0'f32, -100.0, -100.0, -100.0, 0.0, 0.0])
|
let rawLow = makeRaw([-100.0'f32, -100.0, -100.0, -100.0, 0.0, 0.0])
|
||||||
let actsLow = mapActions(rawLow, 0.0, arenaW, arenaH, 600.0, 400.0, 0.0, 0.0, 0.0)
|
let actsLow = mapActions(rawLow, 0.0, arenaW, arenaH, 600.0, 400.0, 0.0, 0.0, 0.0, 600.0, 400.0)
|
||||||
check actsLow.gotoX >= -1e-9, "gotoX >= 0 (low raw)"
|
check actsLow.gotoX >= -1e-9, "gotoX >= 0 (low raw)"
|
||||||
check actsLow.gotoY >= -1e-9, "gotoY >= 0 (low raw)"
|
check actsLow.gotoY >= -1e-9, "gotoY >= 0 (low raw)"
|
||||||
|
|
||||||
@@ -45,24 +45,24 @@ block coordBounds:
|
|||||||
block fireTrigger:
|
block fireTrigger:
|
||||||
# tanh(positive) >= 0 → fire when gunHeat = 0
|
# tanh(positive) >= 0 → fire when gunHeat = 0
|
||||||
let rawFire = makeRaw([0.0'f32, 0.0, 0.0, 0.0, 1.0, 0.0])
|
let rawFire = makeRaw([0.0'f32, 0.0, 0.0, 0.0, 1.0, 0.0])
|
||||||
let actsFire = mapActions(rawFire, 0.0, arenaW, arenaH, 600.0, 400.0, 0.0, 0.0, 0.0)
|
let actsFire = mapActions(rawFire, 0.0, arenaW, arenaH, 600.0, 400.0, 0.0, 0.0, 0.0, 600.0, 400.0)
|
||||||
check actsFire.shouldFire, "positive tanh → should fire when gun cool"
|
check actsFire.shouldFire, "positive tanh → should fire when gun cool"
|
||||||
|
|
||||||
# tanh(negative) < 0 → no fire
|
# tanh(negative) < 0 → no fire
|
||||||
let rawNoFire = makeRaw([0.0'f32, 0.0, 0.0, 0.0, -1.0, 0.0])
|
let rawNoFire = makeRaw([0.0'f32, 0.0, 0.0, 0.0, -1.0, 0.0])
|
||||||
let actsNoFire = mapActions(rawNoFire, 0.0, arenaW, arenaH, 600.0, 400.0, 0.0, 0.0, 0.0)
|
let actsNoFire = mapActions(rawNoFire, 0.0, arenaW, arenaH, 600.0, 400.0, 0.0, 0.0, 0.0, 600.0, 400.0)
|
||||||
check not actsNoFire.shouldFire, "negative tanh → no fire"
|
check not actsNoFire.shouldFire, "negative tanh → no fire"
|
||||||
|
|
||||||
# gunHeat > 0 → no fire even with positive decision
|
# gunHeat > 0 → no fire even with positive decision
|
||||||
let actsHot = mapActions(rawFire, 0.5, arenaW, arenaH, 600.0, 400.0, 0.0, 0.0, 0.0)
|
let actsHot = mapActions(rawFire, 0.5, arenaW, arenaH, 600.0, 400.0, 0.0, 0.0, 0.0, 600.0, 400.0)
|
||||||
check not actsHot.shouldFire, "positive tanh but gun hot → no fire"
|
check not actsHot.shouldFire, "positive tanh but gun hot → no fire"
|
||||||
|
|
||||||
# --- Fire power in [0.1, 3.0] ---
|
# --- Fire power in [0.1, 3.0] ---
|
||||||
block firePowerRange:
|
block firePowerRange:
|
||||||
let rawMin = makeRaw([0.0'f32, 0.0, 0.0, 0.0, 0.0, -100.0])
|
let rawMin = makeRaw([0.0'f32, 0.0, 0.0, 0.0, 0.0, -100.0])
|
||||||
let rawMax = makeRaw([0.0'f32, 0.0, 0.0, 0.0, 0.0, 100.0])
|
let rawMax = makeRaw([0.0'f32, 0.0, 0.0, 0.0, 0.0, 100.0])
|
||||||
let actsMin = mapActions(rawMin, 0.0, arenaW, arenaH, 600.0, 400.0, 0.0, 0.0, 0.0)
|
let actsMin = mapActions(rawMin, 0.0, arenaW, arenaH, 600.0, 400.0, 0.0, 0.0, 0.0, 600.0, 400.0)
|
||||||
let actsMax = mapActions(rawMax, 0.0, arenaW, arenaH, 600.0, 400.0, 0.0, 0.0, 0.0)
|
let actsMax = mapActions(rawMax, 0.0, arenaW, arenaH, 600.0, 400.0, 0.0, 0.0, 0.0, 600.0, 400.0)
|
||||||
check actsMin.firePower >= 0.1 - 1e-6, "firePower >= 0.1"
|
check actsMin.firePower >= 0.1 - 1e-6, "firePower >= 0.1"
|
||||||
check actsMax.firePower <= 3.0 + 1e-6, "firePower <= 3.0"
|
check actsMax.firePower <= 3.0 + 1e-6, "firePower <= 3.0"
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -40,7 +40,7 @@ when isMainModule:
|
|||||||
let raw = randomNormalTensor[float32](ACTION_DIM)
|
let raw = randomNormalTensor[float32](ACTION_DIM)
|
||||||
let speed = 4.0'f32
|
let speed = 4.0'f32
|
||||||
# arena 800×600, bot at centre, heading north, gun north
|
# arena 800×600, bot at centre, heading north, gun north
|
||||||
let botActs = mapActions(raw, 0.0'f32, 800.0, 600.0, 400.0, 300.0, 0.0, speed.float, 0.0) # gunHeat=0 → fire allowed
|
let botActs = mapActions(raw, 0.0'f32, 800.0, 600.0, 400.0, 300.0, 0.0, speed.float, 0.0, 400.0, 300.0) # gunHeat=0 → fire allowed
|
||||||
|
|
||||||
assert botActs.targetSpeed >= -8.0'f32 and botActs.targetSpeed <= 8.0'f32,
|
assert botActs.targetSpeed >= -8.0'f32 and botActs.targetSpeed <= 8.0'f32,
|
||||||
&"targetSpeed out of range: {botActs.targetSpeed}"
|
&"targetSpeed out of range: {botActs.targetSpeed}"
|
||||||
@@ -52,7 +52,7 @@ when isMainModule:
|
|||||||
&"firePower out of range: {botActs.firePower}"
|
&"firePower out of range: {botActs.firePower}"
|
||||||
|
|
||||||
# shouldFire=false when gunHeat > 0
|
# shouldFire=false when gunHeat > 0
|
||||||
let noFire = mapActions(raw, 1.0'f32, 800.0, 600.0, 400.0, 300.0, 0.0, speed.float, 0.0)
|
let noFire = mapActions(raw, 1.0'f32, 800.0, 600.0, 400.0, 300.0, 0.0, speed.float, 0.0, 400.0, 300.0)
|
||||||
assert not noFire.shouldFire, "shouldFire should be false when gunHeat > 0"
|
assert not noFire.shouldFire, "shouldFire should be false when gunHeat > 0"
|
||||||
|
|
||||||
echo "All tests passed"
|
echo "All tests passed"
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -198,9 +198,21 @@ var gIntentGunColor: Color = Color(0)
|
|||||||
var gIntentAdjGunBody: bool = false
|
var gIntentAdjGunBody: bool = false
|
||||||
var gIntentAdjRadarBody: bool = false
|
var gIntentAdjRadarBody: bool = false
|
||||||
var gIntentAdjRadarGun: bool = false
|
var gIntentAdjRadarGun: bool = false
|
||||||
var gIntentTeamMessages: seq[TeamMessage] = @[]
|
# ponytail: static buffers instead of strings/seq. go()'s stop path returns
|
||||||
var gIntentStdOut: string = ""
|
# before buildIntentJson clears these, so a round's last tick can leave heap
|
||||||
var gIntentStdErr: string = ""
|
# blocks owned by the exiting bot thread -> the fresh next-round thread
|
||||||
|
# reallocs a dead allocator block (rawDealloc SIGSEGV). Static storage:
|
||||||
|
# no heap block crosses threads.
|
||||||
|
const INTENT_STDOUT_CAP = 4096
|
||||||
|
const INTENT_STDERR_CAP = 4096
|
||||||
|
const INTENT_MSG_CAP = 16
|
||||||
|
|
||||||
|
var gIntentTeamMessages: array[INTENT_MSG_CAP, TeamMessage]
|
||||||
|
var gIntentTeamMsgsLen: int
|
||||||
|
var gIntentStdOut: array[INTENT_STDOUT_CAP, char]
|
||||||
|
var gIntentStdOutLen: int
|
||||||
|
var gIntentStdErr: array[INTENT_STDERR_CAP, char]
|
||||||
|
var gIntentStdErrLen: int
|
||||||
|
|
||||||
proc buildIntentJson*(): string =
|
proc buildIntentJson*(): string =
|
||||||
## Serialise current intent to JSON for sending to server.
|
## Serialise current intent to JSON for sending to server.
|
||||||
@@ -237,9 +249,10 @@ proc buildIntentJson*(): string =
|
|||||||
obj["tracksColor"] = %gIntentTracksColor.toHex
|
obj["tracksColor"] = %gIntentTracksColor.toHex
|
||||||
if gIntentGunColor != Color(0):
|
if gIntentGunColor != Color(0):
|
||||||
obj["gunColor"] = %gIntentGunColor.toHex
|
obj["gunColor"] = %gIntentGunColor.toHex
|
||||||
if gIntentTeamMessages.len > 0:
|
if gIntentTeamMsgsLen > 0:
|
||||||
var msgs = newJArray()
|
var msgs = newJArray()
|
||||||
for m in gIntentTeamMessages:
|
for i in 0 ..< gIntentTeamMsgsLen:
|
||||||
|
let m = gIntentTeamMessages[i]
|
||||||
var mo = newJObject()
|
var mo = newJObject()
|
||||||
mo["message"] = %m.message
|
mo["message"] = %m.message
|
||||||
mo["messageType"] = %m.messageType
|
mo["messageType"] = %m.messageType
|
||||||
@@ -247,13 +260,14 @@ proc buildIntentJson*(): string =
|
|||||||
mo["receiverId"] = %m.receiverId
|
mo["receiverId"] = %m.receiverId
|
||||||
msgs.add mo
|
msgs.add mo
|
||||||
obj["teamMessages"] = msgs
|
obj["teamMessages"] = msgs
|
||||||
gIntentTeamMessages.setLen 0
|
for i in 0 ..< gIntentTeamMsgsLen: gIntentTeamMessages[i].reset
|
||||||
if gIntentStdOut.len > 0:
|
gIntentTeamMsgsLen = 0
|
||||||
obj["stdOut"] = %gIntentStdOut
|
if gIntentStdOutLen > 0:
|
||||||
gIntentStdOut = ""
|
obj["stdOut"] = %($gIntentStdOut[0 ..< gIntentStdOutLen])
|
||||||
if gIntentStdErr.len > 0:
|
gIntentStdOutLen = 0
|
||||||
obj["stdErr"] = %gIntentStdErr
|
if gIntentStdErrLen > 0:
|
||||||
gIntentStdErr = ""
|
obj["stdErr"] = %($gIntentStdErr[0 ..< gIntentStdErrLen])
|
||||||
|
gIntentStdErrLen = 0
|
||||||
let svg = svgOutput()
|
let svg = svgOutput()
|
||||||
if svg.len > 0:
|
if svg.len > 0:
|
||||||
obj["debugGraphics"] = %svg
|
obj["debugGraphics"] = %svg
|
||||||
@@ -311,19 +325,27 @@ proc setGunColor*(color: Color) = gIntentGunColor = color
|
|||||||
|
|
||||||
proc printToStdOut*(s: string) =
|
proc printToStdOut*(s: string) =
|
||||||
## Append s to this tick's stdOut payload (sent to server in BotIntent).
|
## Append s to this tick's stdOut payload (sent to server in BotIntent).
|
||||||
gIntentStdOut.add s
|
let n = min(s.len, INTENT_STDOUT_CAP - gIntentStdOutLen)
|
||||||
|
for i in 0 ..< n: gIntentStdOut[gIntentStdOutLen + i] = s[i]
|
||||||
|
inc gIntentStdOutLen, n
|
||||||
|
|
||||||
proc printToStdErr*(s: string) =
|
proc printToStdErr*(s: string) =
|
||||||
## Append s to this tick's stdErr payload (sent to server in BotIntent).
|
## Append s to this tick's stdErr payload (sent to server in BotIntent).
|
||||||
gIntentStdErr.add s
|
let n = min(s.len, INTENT_STDERR_CAP - gIntentStdErrLen)
|
||||||
|
for i in 0 ..< n: gIntentStdErr[gIntentStdErrLen + i] = s[i]
|
||||||
|
inc gIntentStdErrLen, n
|
||||||
|
|
||||||
proc broadcastTeamMessage*(message: string) =
|
proc broadcastTeamMessage*(message: string) =
|
||||||
## Send a message to all teammates this tick.
|
## Send a message to all teammates this tick.
|
||||||
gIntentTeamMessages.add TeamMessage(message: message, messageType: "String")
|
if gIntentTeamMsgsLen < INTENT_MSG_CAP:
|
||||||
|
gIntentTeamMessages[gIntentTeamMsgsLen] = TeamMessage(message: message, messageType: "String")
|
||||||
|
inc gIntentTeamMsgsLen
|
||||||
|
|
||||||
proc sendTeamMessage*(botId: int; message: string) =
|
proc sendTeamMessage*(botId: int; message: string) =
|
||||||
## Send a message to a specific teammate this tick.
|
## Send a message to a specific teammate this tick.
|
||||||
gIntentTeamMessages.add TeamMessage(message: message, messageType: "String", receiverId: botId)
|
if gIntentTeamMsgsLen < INTENT_MSG_CAP:
|
||||||
|
gIntentTeamMessages[gIntentTeamMsgsLen] = TeamMessage(message: message, messageType: "String", receiverId: botId)
|
||||||
|
inc gIntentTeamMsgsLen
|
||||||
|
|
||||||
proc setAdjustGunForBodyTurn*(v: bool) = gIntentAdjGunBody = v
|
proc setAdjustGunForBodyTurn*(v: bool) = gIntentAdjGunBody = v
|
||||||
proc setAdjustRadarForBodyTurn*(v: bool) = gIntentAdjRadarBody = v
|
proc setAdjustRadarForBodyTurn*(v: bool) = gIntentAdjRadarBody = v
|
||||||
@@ -808,6 +830,16 @@ proc botThreadEntry() {.thread.} =
|
|||||||
" prevDir=" & $gPreviousDirection &
|
" prevDir=" & $gPreviousDirection &
|
||||||
" prevGunDir=" & $gPreviousGunDirection)
|
" prevGunDir=" & $gPreviousGunDirection)
|
||||||
|
|
||||||
|
# Reset graphics + intent buffers on the thread that owns them. go()'s
|
||||||
|
# stop path (round end) returns before buildIntentJson/clearGraphics, so
|
||||||
|
# the previous round's thread can leave content behind; resetting here
|
||||||
|
# keeps it from leaking into this round's first intent.
|
||||||
|
clearGraphics()
|
||||||
|
gIntentStdOutLen = 0
|
||||||
|
gIntentStdErrLen = 0
|
||||||
|
for i in 0 ..< gIntentTeamMsgsLen: gIntentTeamMessages[i].reset
|
||||||
|
gIntentTeamMsgsLen = 0
|
||||||
|
|
||||||
dispatchPendingEvents(gBot) # dispatch events embedded in the first tick
|
dispatchPendingEvents(gBot) # dispatch events embedded in the first tick
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -9,13 +9,29 @@ import ./color
|
|||||||
# Module-level state (single bot per process)
|
# Module-level state (single bot per process)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
var gSvgBuffer: string
|
# ponytail: static char array + length instead of a heap string. A fresh bot
|
||||||
|
# thread runs each round; a module-level string grown by thread N and cleared
|
||||||
|
# ("") by thread N+1 free/reallocs a dead thread's allocator block ->
|
||||||
|
# rawDealloc SIGSEGV (same crash class as the event queue seq; gdb-confirmed
|
||||||
|
# in drawText mid-campaign). Static storage: no heap block crosses threads.
|
||||||
|
const SVG_BUFFER_CAP = 16384
|
||||||
|
|
||||||
|
var gSvgLen: int
|
||||||
|
var gSvgBuffer: array[SVG_BUFFER_CAP, char]
|
||||||
var gStrokeColor: Color = WHITE
|
var gStrokeColor: Color = WHITE
|
||||||
var gFillColor: Color = WHITE
|
var gFillColor: Color = WHITE
|
||||||
var gStrokeWidth: float = 1.0
|
var gStrokeWidth: float = 1.0
|
||||||
var gFontFamily: string = "Arial"
|
var gFontFamily: string = "Arial" # never rebound at runtime (setFont unused)
|
||||||
var gFontSize: float = 12.0
|
var gFontSize: float = 12.0
|
||||||
|
|
||||||
|
proc appendSvg(s: string) =
|
||||||
|
## Append an SVG fragment, dropping anything past the static cap.
|
||||||
|
let room = SVG_BUFFER_CAP - gSvgLen
|
||||||
|
if room > 0:
|
||||||
|
let n = min(room, s.len)
|
||||||
|
for i in 0 ..< n: gSvgBuffer[gSvgLen + i] = s[i]
|
||||||
|
inc gSvgLen, n
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Internal helpers
|
# Internal helpers
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -26,12 +42,12 @@ proc svgAttrs(): string =
|
|||||||
|
|
||||||
proc svgOutput*(): string =
|
proc svgOutput*(): string =
|
||||||
## Returns the SVG fragment for this tick, or "" if nothing was drawn.
|
## Returns the SVG fragment for this tick, or "" if nothing was drawn.
|
||||||
if gSvgBuffer.len == 0: return ""
|
if gSvgLen == 0: return ""
|
||||||
"<g>" & gSvgBuffer & "</g>"
|
"<g>" & $gSvgBuffer[0 ..< gSvgLen] & "</g>"
|
||||||
|
|
||||||
proc clearGraphics*() =
|
proc clearGraphics*() =
|
||||||
## Reset buffer and all style globals to defaults. Called after each tick.
|
## Reset buffer and all style globals to defaults. Called after each tick.
|
||||||
gSvgBuffer = ""
|
gSvgLen = 0
|
||||||
gStrokeColor = WHITE
|
gStrokeColor = WHITE
|
||||||
gFillColor = WHITE
|
gFillColor = WHITE
|
||||||
gStrokeWidth = 1.0
|
gStrokeWidth = 1.0
|
||||||
@@ -54,26 +70,26 @@ proc setFont*(family: string; size: float) =
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
proc drawLine*(x1, y1, x2, y2: float) =
|
proc drawLine*(x1, y1, x2, y2: float) =
|
||||||
gSvgBuffer.add &"<line x1=\"{x1}\" y1=\"{y1}\" x2=\"{x2}\" y2=\"{y2}\" {svgAttrs()}/>"
|
appendSvg(&"<line x1=\"{x1}\" y1=\"{y1}\" x2=\"{x2}\" y2=\"{y2}\" {svgAttrs()}/>")
|
||||||
|
|
||||||
proc drawRectangle*(x, y, w, h: float) =
|
proc drawRectangle*(x, y, w, h: float) =
|
||||||
let attrs = &"stroke=\"{gStrokeColor.toHex}\" fill=\"none\" stroke-width=\"{gStrokeWidth}\""
|
let attrs = &"stroke=\"{gStrokeColor.toHex}\" fill=\"none\" stroke-width=\"{gStrokeWidth}\""
|
||||||
gSvgBuffer.add &"<rect x=\"{x}\" y=\"{y}\" width=\"{w}\" height=\"{h}\" {attrs}/>"
|
appendSvg(&"<rect x=\"{x}\" y=\"{y}\" width=\"{w}\" height=\"{h}\" {attrs}/>")
|
||||||
|
|
||||||
proc fillRectangle*(x, y, w, h: float) =
|
proc fillRectangle*(x, y, w, h: float) =
|
||||||
let attrs = &"stroke=\"none\" fill=\"{gFillColor.toHex}\""
|
let attrs = &"stroke=\"none\" fill=\"{gFillColor.toHex}\""
|
||||||
gSvgBuffer.add &"<rect x=\"{x}\" y=\"{y}\" width=\"{w}\" height=\"{h}\" {attrs}/>"
|
appendSvg(&"<rect x=\"{x}\" y=\"{y}\" width=\"{w}\" height=\"{h}\" {attrs}/>")
|
||||||
|
|
||||||
proc drawCircle*(x, y, r: float) =
|
proc drawCircle*(x, y, r: float) =
|
||||||
let attrs = &"stroke=\"{gStrokeColor.toHex}\" fill=\"none\" stroke-width=\"{gStrokeWidth}\""
|
let attrs = &"stroke=\"{gStrokeColor.toHex}\" fill=\"none\" stroke-width=\"{gStrokeWidth}\""
|
||||||
gSvgBuffer.add &"<circle cx=\"{x}\" cy=\"{y}\" r=\"{r}\" {attrs}/>"
|
appendSvg(&"<circle cx=\"{x}\" cy=\"{y}\" r=\"{r}\" {attrs}/>")
|
||||||
|
|
||||||
proc fillCircle*(x, y, r: float) =
|
proc fillCircle*(x, y, r: float) =
|
||||||
let attrs = &"stroke=\"none\" fill=\"{gFillColor.toHex}\""
|
let attrs = &"stroke=\"none\" fill=\"{gFillColor.toHex}\""
|
||||||
gSvgBuffer.add &"<circle cx=\"{x}\" cy=\"{y}\" r=\"{r}\" {attrs}/>"
|
appendSvg(&"<circle cx=\"{x}\" cy=\"{y}\" r=\"{r}\" {attrs}/>")
|
||||||
|
|
||||||
proc drawText*(text: string; x, y: float) =
|
proc drawText*(text: string; x, y: float) =
|
||||||
gSvgBuffer.add &"<text x=\"{x}\" y=\"{y}\" font-family=\"{gFontFamily}\" font-size=\"{gFontSize}\">{text}</text>"
|
appendSvg(&"<text x=\"{x}\" y=\"{y}\" font-family=\"{gFontFamily}\" font-size=\"{gFontSize}\">{text}</text>")
|
||||||
|
|
||||||
proc drawPolygon*(points: seq[(float, float)]) =
|
proc drawPolygon*(points: seq[(float, float)]) =
|
||||||
var pts = ""
|
var pts = ""
|
||||||
@@ -81,7 +97,7 @@ proc drawPolygon*(points: seq[(float, float)]) =
|
|||||||
if pts.len > 0: pts.add ' '
|
if pts.len > 0: pts.add ' '
|
||||||
pts.add &"{px},{py}"
|
pts.add &"{px},{py}"
|
||||||
let attrs = &"stroke=\"{gStrokeColor.toHex}\" fill=\"none\" stroke-width=\"{gStrokeWidth}\""
|
let attrs = &"stroke=\"{gStrokeColor.toHex}\" fill=\"none\" stroke-width=\"{gStrokeWidth}\""
|
||||||
gSvgBuffer.add &"<polygon points=\"{pts}\" {attrs}/>"
|
appendSvg(&"<polygon points=\"{pts}\" {attrs}/>")
|
||||||
|
|
||||||
proc fillPolygon*(points: seq[(float, float)]) =
|
proc fillPolygon*(points: seq[(float, float)]) =
|
||||||
var pts = ""
|
var pts = ""
|
||||||
@@ -89,4 +105,4 @@ proc fillPolygon*(points: seq[(float, float)]) =
|
|||||||
if pts.len > 0: pts.add ' '
|
if pts.len > 0: pts.add ' '
|
||||||
pts.add &"{px},{py}"
|
pts.add &"{px},{py}"
|
||||||
let attrs = &"stroke=\"none\" fill=\"{gFillColor.toHex}\""
|
let attrs = &"stroke=\"none\" fill=\"{gFillColor.toHex}\""
|
||||||
gSvgBuffer.add &"<polygon points=\"{pts}\" {attrs}/>"
|
appendSvg(&"<polygon points=\"{pts}\" {attrs}/>")
|
||||||
|
|||||||
Reference in New Issue
Block a user