Add threading support to LoadableResource.

This commit is contained in:
Alberto Torres 2024-08-29 00:19:52 +02:00
parent e748831f3a
commit 78cafe6566
2 changed files with 60 additions and 12 deletions

View file

@ -58,12 +58,14 @@ type LoadableResource* = ref object of RootObj
done_func: proc() done_func: proc()
cancel_func: proc() cancel_func: proc()
str*: proc(): string str*: proc(): string
use_threads: bool
result: ref Result # only to be used with loadAll result: ref Result # only to be used with loadAll
proc newLoadableResource*[T: LoadableResource]( proc newLoadableResource*[T: LoadableResource](
start: proc(self: LoadableResource), start: proc(self: LoadableResource),
done: proc() = nil, done: proc() = nil,
str: proc(): string = nil, str: proc(): string = nil,
use_threads = false,
): T = ): T =
new(result) new(result)
result.start_func = start result.start_func = start
@ -71,10 +73,28 @@ proc newLoadableResource*[T: LoadableResource](
result.str = str result.str = str
if str == nil: if str == nil:
result.str = proc(): string = "" result.str = proc(): string = ""
result.use_threads = use_threads
# main -> thread channels
var to_start: Channel[LoadableResource]
# main <- thread channels
var to_return: Channel[(LoadableResource, bool, string, pointer, int)]
var to_done: Channel[LoadableResource]
proc start*[T: LoadableResource](self: T) = proc start*[T: LoadableResource](self: T) =
self.status = Started self.status = Started
self.start_func(self) if self.use_threads:
to_start.send self
else:
self.start_func(self)
proc doneImpl*[T: LoadableResource](self: T) =
if self.status == Started:
self.cancel()
else:
self.status = NotStarted
if self.done_func != nil:
self.done_func()
proc `onload=`*[T: LoadableResource](self: T, onload_func: proc(ok: bool, err: string, p: pointer, len: int)) = proc `onload=`*[T: LoadableResource](self: T, onload_func: proc(ok: bool, err: string, p: pointer, len: int)) =
self.onload_func = onload_func self.onload_func = onload_func
@ -85,25 +105,24 @@ proc onload*[T: LoadableResource](self: T, ok: bool, err: string, p: pointer, le
if self.result != nil: if self.result != nil:
self.result[] = (ok, err, p, len) self.result[] = (ok, err, p, len)
if self.onload_func != nil: if self.onload_func != nil:
self.onload_func(ok, err, p, len) if self.use_threads:
to_return.send((self.LoadableResource, ok, err, p, len))
else:
self.onload_func(ok, err, p, len)
else: # cancelled else: # cancelled
self.status = NotStarted self.doneImpl()
if self.done_func != nil:
self.done_func()
# TODO: check if we can always use destructors instead of calling this # TODO: check if we can always use destructors instead of calling this
proc done*[T: LoadableResource](self: T) = proc done*[T: LoadableResource](self: T) =
if self.status == Started: if self.use_threads:
self.cancel() to_done.send self
else: else:
self.status = NotStarted self.doneImpl()
if self.done_func != nil:
self.done_func()
proc cancel*[T: LoadableResource](self: T) = proc cancel*[T: LoadableResource](self: T) =
if self.status != Started: if self.status != Started:
return return
if self.cancel_func != nil: if self.cancel_func == nil:
# self.`onload=`(proc(ok, err, p, len: auto) = # self.`onload=`(proc(ok, err, p, len: auto) =
# self.done()) # self.done())
self.onload_func = proc(ok: bool, err: string, p: pointer, len: int) = self.onload_func = proc(ok: bool, err: string, p: pointer, len: int) =
@ -128,6 +147,31 @@ proc cancel*[T: LoadableResource](self: T) =
# results.add res.result[] # results.add res.result[]
# res.result = nil # res.result = nil
var worker: Thread[void]
proc workerThreadProc() {.thread.} =
while true:
let res = to_start.recv()
if res == nil:
break
cast[proc(self: LoadableResource) {.gcsafe.}](res.start_func)(res)
worker.createThread(workerThreadProc)
to_start.open()
to_return.open()
to_done.open()
proc updateLoadableWorkerThreads*() =
while true:
let tried = to_return.tryRecv()
if not tried.dataAvailable:
break
let (res, ok, err, p, len) = tried.msg
res.onload_func(ok, err, p, len)
while true:
let tried = to_done.tryRecv()
if not tried.dataAvailable:
break
tried.msg.done_func()
type Fetch* = ref object of LoadableResource type Fetch* = ref object of LoadableResource
custom: pointer custom: pointer
@ -226,6 +270,7 @@ proc loadUri*(
var done_func: proc() var done_func: proc()
var self: Fetch var self: Fetch
var uri = uri var uri = uri
var use_threads = false
when not defined(onlyLocalFiles): when not defined(onlyLocalFiles):
when defined(emscripten): when defined(emscripten):
const is_remote = true const is_remote = true
@ -261,6 +306,7 @@ proc loadUri*(
discard emscripten_fetch_close(cast[ptr emscripten_fetch_t](self.custom)) discard emscripten_fetch_close(cast[ptr emscripten_fetch_t](self.custom))
self.custom = nil self.custom = nil
else: else:
use_threads = true
var client = newHttpClient() var client = newHttpClient()
var response: string var response: string
start_func = proc(self: LoadableResource) = start_func = proc(self: LoadableResource) =
@ -298,7 +344,7 @@ proc loadUri*(
if cast[Fetch](self).auto_done: if cast[Fetch](self).auto_done:
self.done() self.done()
proc str(): string = uri proc str(): string = uri
self = newLoadableResource[Fetch](start_func, done_func, str) self = newLoadableResource[Fetch](start_func, done_func, str, use_threads=use_threads)
self.onload_func = onload_func self.onload_func = onload_func
self.auto_done = auto_done self.auto_done = auto_done
if auto_start: if auto_start:

View file

@ -77,6 +77,7 @@ import ./screen
import ./platform/platform import ./platform/platform
import ./loaders/blend import ./loaders/blend
import ./util import ./util
from loadable import updateLoadableWorkerThreads
import arr_ref import arr_ref
export arr_ref export arr_ref
@ -165,6 +166,7 @@ proc myou_main_loop*(self: MyouEngine) =
## ##
## You usually don't need to call this. Use `run <#run,MyouEngine>`_ ## You usually don't need to call this. Use `run <#run,MyouEngine>`_
## instead. ## instead.
updateLoadableWorkerThreads()
# TODO: make a table object that can be iterated while changing, e.g. with a # TODO: make a table object that can be iterated while changing, e.g. with a
# seq and a dirty flag to update the seq # seq and a dirty flag to update the seq
if self.new_scenes.len != 0: if self.new_scenes.len != 0: