Skip to content

Commit

Permalink
Update to sigils threads (#66)
Browse files Browse the repository at this point in the history
* add ptr type for unsafeWeakRef
* cleanup
* refactor threads
* refactor thread impl to use sigils threads
  • Loading branch information
elcritch authored Jan 11, 2025
1 parent 5cb4e3f commit 5ee986d
Show file tree
Hide file tree
Showing 27 changed files with 169 additions and 141 deletions.
7 changes: 4 additions & 3 deletions config.nims
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ switch("styleCheck", "hint")

--path:"$nim" ## important for nimscripter

--gc:orc
# --gc:arc
# --gc:orc
--gc:arc
--d:useMalloc

--d:windyNoHttp
--d:printDebugTimings
--d:nimStrictDelete
# --d:nimStrictDelete
--deepcopy:on

--hint:"ConvFromXtoItselfNotNeeded:off"
Expand Down
3 changes: 2 additions & 1 deletion figuro.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ requires "nimscripter >= 1.1.5"
requires "msgpack4nim"
requires "stack_strings"
requires "micros"
requires "https://github.com/elcritch/windy#figuro-fixes"
# requires "windy"
requires "https://github.com/elcritch/windy#macos-keyinput-fixes"
7 changes: 4 additions & 3 deletions figuro/common/nodes/ui.nim
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,19 @@ type
Theme* = ref object
font*: UiFont

AppFrame* = ref object
AppFrame* = ref object of Agent
frameRunner*: AgentProcTy[tuple[]]
appTicker*: AgentProxyShared
redrawNodes*: OrderedSet[Figuro]
root*: Figuro
uxInputList*: Chan[AppInputs]
# threadAgents*: seq[ThreadAgent]
running*, focused*, minimized*, fullscreen*: bool

windowSize*: Box ## Screen size in logical coordinates.
windowRawSize*: Vec2 ## Screen coordinates

Figuro* = ref object of Agent
frame*: AppFrame
frame*: WeakRef[AppFrame]
parent*: WeakRef[Figuro]
uid*: NodeID
name*: string
Expand Down
93 changes: 53 additions & 40 deletions figuro/exec.nim
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ else:
export opengl

import std/os
import std/sharedtables

import sigils
import sigils/threads

import shared, internal
import ui/[core, events]
import common/nodes/ui
import common/nodes/render
import common/nodes/transfer
import widget
import timers

Expand All @@ -25,61 +25,74 @@ export core, events
when not compileOption("threads"):
{.error: "This module requires --threads:on compilation flag".}

when not compiles(AppFrame().deepCopy()):
{.error: "This module requires --deepcopy:on compilation flag".}

when not defined(gcArc) and not defined(gcOrc) and not defined(nimdoc):
{.error: "Figuro requires --gc:arc or --gc:orc".}

var
runFrame*: proc(frame: AppFrame) {.nimcall.}
appFrames*: Table[AppFrame, Renderer]
# runFrame*: proc(frame: AppFrame) {.nimcall.}
appFrames*: Table[WeakRef[AppFrame], Renderer]
uxInputList*: Chan[AppInputs]

const
renderPeriodMs* {.intdefine.} = 14
renderDuration* = initDuration(milliseconds = renderPeriodMs)

const renderPeriodMs {.intdefine.} = 16
const appPeriodMs {.intdefine.} = 16
var appTickThread*: ptr SigilThreadImpl
var appThread*: ptr SigilThreadImpl

var frameTickThread, appTickThread: Thread[void]
var appThread, : Thread[AppFrame]
type
AppTicker* = ref object of Agent
period*: Duration

proc renderTicker() {.thread.} =
while true:
uiRenderEvent.trigger()
os.sleep(appPeriodMs - 2)
app.tickCount.inc()
if app.tickCount == app.tickCount.typeof.high:
app.tickCount = 0
proc appTick*(tp: AppTicker) {.signal.}

proc appTicker() {.thread.} =
proc tick*(self: AppTicker) {.slot.} =
echo "start tick"
printConnections(self)
while app.running:
uiAppEvent.trigger()
os.sleep(renderPeriodMs - 2)

proc runApplication(frame: AppFrame) {.thread.} =
{.gcsafe.}:
while app.running:
wait(uiAppEvent)
timeIt(appAvgTime):
runFrame(frame)
app.frameCount.inc()
emit self.appTick()
os.sleep(self.period.inMilliseconds)

proc setupTicker(frame: AppFrame) =
appTickThread = newSigilThread()
var ticker = AppTicker(period: renderDuration)
when defined(sigilsDebug): ticker.debugName = "Ticker"
let tp = ticker.moveToThread(appTickThread)
connect(tp, appTick, frame, frame.frameRunner)
connect(appTickThread[].agent, started, tp, tick)
appTickThread.start()
frame.appTicker = tp

proc start(self: AppFrame) {.slot.} =
self.setupTicker()

proc runRenderer(renderer: Renderer) =
while app.running and renderer.frame.running:
wait(uiRenderEvent)
while app.running and renderer[].frame[].running:
app.tickCount.inc()
if app.tickCount == app.tickCount.typeof.high:
app.tickCount = 0
timeIt(renderAvgTime):
renderer.render(true)

proc setupFrame*(frame: AppFrame): Renderer =
let renderer = setupRenderer(frame)
appFrames[frame] = renderer
result = renderer
renderer.render(false)
os.sleep(renderDuration.inMilliseconds)

proc run*(frame: AppFrame) =
let renderer = setupFrame(frame)
proc run*(frame: var AppFrame, frameRunner: AgentProcTy[tuple[]]) =
## run figuro
when defined(sigilsDebug): frame.debugName = "Frame"
let frameRef = frame.unsafeWeakRef()
let renderer = setupRenderer(frameRef)
appFrames[frameRef] = renderer
frame.frameRunner = frameRunner

uiRenderEvent = initUiEvent()
uiAppEvent = initUiEvent()

createThread(frameTickThread, renderTicker)
createThread(appTickThread, appTicker)
createThread(appThread, runApplication, frame)
appThread = newSigilThread()
let frameProxy = frame.moveToThread(ensureMove appThread)
connect(appThread[].agent, started, frameProxy, start)
appThread.start()

proc ctrlc() {.noconv.} =
echo "Got Ctrl+C exiting!"
Expand Down
77 changes: 38 additions & 39 deletions figuro/execApps.nim
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@

import std/locks
import std/sets
import pkg/threading/atomics
import shared, ui/core
import common/nodes/transfer
import common/nodes/ui as ui
import common/nodes/render as render
import widget
import sigils
import sigils/threads

import exec

Expand All @@ -15,48 +18,44 @@ when not compileOption("threads"):
when not defined(gcArc) and not defined(gcOrc) and not defined(nimdoc):
{.error: "Figuro requires --gc:arc or --gc:orc".}

proc runFrameImpl(frame: AppFrame) =
# Ticks
emit frame.root.doTick(app.tickCount, getMonoTime())

# Events
var input: AppInputs
## only process up to ~10 events at a time
var cnt = 20
while frame.uxInputList.tryRecv(input) and cnt > 0:
uxInputs = input
computeEvents(frame)
cnt.dec()

# Main
frame.root.diffIndex = 0
if app.requestedFrame > 0:
refresh(frame.root)
app.requestedFrame.dec()

if frame.redrawNodes.len() > 0:
computeEvents(frame)
let rn = frame.redrawNodes
for node in rn:
emit node.doDraw()
frame.redrawNodes.clear()
computeLayout(frame.root)
computeScreenBox(nil, frame.root)
appFrames.withValue(frame, renderer):
withLock(renderer.lock):
renderer.updated = true
renderer.nodes = frame.root.copyInto()

exec.runFrame = runFrameImpl
proc runFrameImpl(frame: AppFrame) {.slot.} =
# Ticks
emit frame.root.doTick(app.tickCount, getMonoTime())

# Events
var input: AppInputs
## only process up to ~X events at a time
var cnt = 4
while frame.uxInputList.tryRecv(input) and cnt > 0:
uxInputs = input
computeEvents(frame)
cnt.dec()

# Main
frame.root.diffIndex = 0
if app.requestedFrame > 0:
refresh(frame.root)
app.requestedFrame.dec()

if frame.redrawNodes.len() > 0:
computeEvents(frame)
let rn = frame.redrawNodes
for node in rn:
emit node.doDraw()
frame.redrawNodes.clear()
computeLayout(frame.root)
computeScreenBox(nil, frame.root)
appFrames.withValue(frame.unsafeWeakRef(), renderer):
withLock(renderer.lock):
renderer.nodes = frame.root.copyInto()
renderer.updated.store true

# exec.runFrame = runFrameImpl

proc startFiguro*(
frame: AppFrame,
frame: var AppFrame,
) =
## Starts Fidget UI library
##

# app.fullscreen = fullscreen
# if not fullscreen:

# let frame = newAppFrame(widget)
run(frame)
run(frame, AppFrame.runFrameImpl())
1 change: 1 addition & 0 deletions figuro/inputs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ const

type
AppInputs* = object
empty*: bool
mouse*: Mouse
keyboard*: Keyboard

Expand Down
9 changes: 5 additions & 4 deletions figuro/renderer/opengl.nim
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import std/terminal

import pkg/pixie
import pkg/windy
import pkg/sigils/weakrefs

import ../shared
import ../inputs
Expand Down Expand Up @@ -31,11 +32,11 @@ proc configureWindowEvents(renderer: Renderer) =
updateWindowSize(renderer.frame, window)
renderer.render(updated = true, poll = false)
var uxInput = window.copyInputs()
uxInput.windowSize = some renderer.frame.windowSize
uxInput.windowSize = some renderer.frame[].windowSize
discard renderer.uxInputList.trySend(uxInput)

window.onFocusChange = proc () =
renderer.frame.focused = window.focused
renderer.frame[].focused = window.focused
let uxInput = window.copyInputs()
discard renderer.uxInputList.trySend(uxInput)

Expand Down Expand Up @@ -94,9 +95,9 @@ proc configureWindowEvents(renderer: Renderer) =
{styleDim}, fgGreen, $rune)
discard renderer.uxInputList.trySend(uxInput)

renderer.frame.running = true
renderer.frame[].running = true

proc setupRenderer*[F](frame: F): Renderer =
proc setupRenderer*[F](frame: WeakRef[F]): Renderer =
let window = newWindow("", ivec2(1280, 800))
let atlasSize = 1024 shl (app.uiScale.round().toInt() + 1)
let renderer = newRenderer(frame, window, false, 1.0, atlasSize)
Expand Down
1 change: 0 additions & 1 deletion figuro/renderer/opengl/fontutils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ proc getId*(typeface: Typeface): TypefaceId =
# FontId font.hash()

iterator glyphs*(arrangement: GlyphArrangement): GlyphPosition =
# threads: RenderThread

var idx = 0
# if arrangement != nil:
Expand Down
22 changes: 14 additions & 8 deletions figuro/renderer/opengl/renderer.nim
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import std/[hashes, os, strformat, tables, times, unicode]

import pkg/threading/atomics
import pkg/chroma
import pkg/windy
import pkg/opengl
from pixie import Image
import pkg/sigils
import pkg/sigils/threads

import window

Expand All @@ -19,13 +22,13 @@ type
ctx*: Context
window*: Window
uxInputList*: Chan[AppInputs]
frame*: AppFrame
frame*: WeakRef[AppFrame]
lock*: Lock
updated*: bool
updated*: Atomic[bool]
nodes*: RenderNodes

proc newRenderer*(
frame: AppFrame,
frame: WeakRef[AppFrame],
window: Window,
pixelate: bool,
forcePixelScale: float32,
Expand All @@ -38,9 +41,9 @@ proc newRenderer*(
renderer.ctx = newContext(atlasSize = atlasSize,
pixelate = pixelate,
pixelScale = app.pixelScale)
renderer.uxInputList = newChan[AppInputs](40)
renderer.uxInputList = newChan[AppInputs](4)
renderer.lock.initLock()
frame.uxInputList = renderer.uxInputList
frame[].uxInputList = renderer.uxInputList
return renderer

proc renderDrawable*(ctx: Context, node: Node) =
Expand Down Expand Up @@ -248,7 +251,7 @@ proc renderRoot*(ctx: Context, nodes: var RenderNodes) {.forbids: [MainThreadEff
proc renderFrame*(renderer: Renderer) =
let ctx: Context = renderer.ctx
clearColorBuffer(color(1.0, 1.0, 1.0, 1.0))
ctx.beginFrame(renderer.frame.windowRawSize)
ctx.beginFrame(renderer.frame[].windowRawSize)
ctx.saveTransform()
ctx.scale(ctx.pixelScale)

Expand Down Expand Up @@ -284,16 +287,19 @@ proc renderAndSwap(renderer: Renderer,
proc render*(renderer: Renderer, updated = false, poll = true) =
## renders and draws a window given set of nodes passed
## in via the Renderer object
let update = renderer.updated or updated
let
renderUpdate = renderer.updated.load()
update = renderUpdate or updated

if renderer.window.closeRequested:
renderer.frame.running = false
renderer.frame[].running = false
return

timeIt(eventPolling):
if poll:
windy.pollEvents()

if update:
renderer.updated.store false
renderAndSwap(renderer, update)

Loading

0 comments on commit 5ee986d

Please sign in to comment.