So I read the manual about proxies and experimented a bit with them. I already understood, they are connected to a higher memory load. But i did not notice anything mention about them being handled in a seperate thread?
I am new to this two topics and hope this question is not too foolish
Hi @j0-ji !
No the proxies arenāt updated on a separate thread.
Is there anything specific you are trying to do?
Hi @Mathias_Westerdahl
Thanks for the quick answer.
I am looking for ways to improve my code regarding my last question
creating open world (with chunks) (with GitHub-link in comments)
I had the issue that i already had a small lag while running around the map, when I tried to generate a second layer with just trees. Im still exploring procedural generation, and in the GitHub-example I have 2 layers with 6400 tiles each(25 chunks; 16x16 tiles), wich are all generated again via fractal noise and updated (tilemaps) when the player changes his current chunk.
So my first thought was (even if it would not solve the underlying problem of inefficiency) to outsource the terrain generation to a second thread. Are my thoughts on the right path for now?
Currently I am also trying to optimize the code itself, and to minimize the updates done after a chunk-change.
[edit: spelling ^^ā]
Iād say yes, if you wish to avoid some loading lag, you might want to look into threading this.
In my own 3D terrain example, I use a thread to generate noise, and load it into our Buffer api from a thread.
However, Iām not sure what the best approach would be for you since we have no good way to update a tile map from C. Also, Iād check where that time is actually spent (e.g the noise generation, or the setting of the tiles) using socket.gettime()
.
Also, it might help to create smaller chunks or patches each time.
Hereās how I do something very similar to what you are describing:
local thread = require "thread"
local limit = 1/60
local t = socket.gettime()
local last_pause = socket.gettime()
local function check_pause()
t = socket.gettime()
if t - last_pause > limit then
last_pause = t
return true
end
return false
end
local function generate_world(thread_pause)
--do world generation
for row = min_row, max_row do
for col = min_col, max_col do
--stuff here
end
if check_pause() then thread_pause() end
end
--second pass
for row = min_row, max_row do
for col = min_col, max_col do
--stuff here
end
if check_pause() then thread_pause() end
end
--etc
end
function init(self)
local world_thread = thread.create(generate_world, function(thread_completed, thread_function_return_value)
-- print("completed?", thread_completed)
end)
end
The module is this, helpfully provided by @britzl :
local M = {}
local threads = {}
local id = 0
local handle = nil
local function start_timer()
if handle then
return
end
handle = timer.delay(0, true, function(self, handle, dt)
for id, thread in pairs(threads) do
if thread.cancelled then
threads[id] = nil
else
local status = coroutine.status(thread.co)
if status == "suspended" then
local ok, result = coroutine.resume(thread.co)
if not ok then
threads[id] = nil
pcall(thread.cb, false, result)
print("Thread error callback [suspended]: ", result)
end
elseif status == "dead" then
threads[id] = nil
pcall(thread.cb, true, thread.result)
end
end
end
if not next(threads) then
timer.cancel(handle)
handle = nil
end
end)
end
function M.create(fn, cb)
assert(fn, "You must provide a function to run")
assert(cb, "You must provide a result callback")
id = id + 1
local thread = {
id = id,
cb = cb,
}
thread.co = coroutine.create(function()
local ok, result = pcall(fn, coroutine.yield)
if not ok then
pcall(cb, false, result)
print("Thread error callback [create]: ", result)
else
thread.result = result
end
end)
threads[id] = thread
local ok, result = coroutine.resume(thread.co)
if not ok then
threads[id] = nil
pcall(cb, false, result)
print("Thread error callback [resume]: ", result)
return id
end
start_timer()
return id
end
function M.cancel(id)
local t = threads[id]
assert(t)
t.cancelled = true
end
return M
The logic for your check_pause() function is up to you, as is the frequency with which you call it. Every row works well for me.
Thanks for the replies.
I spent yesterday and some time today, trying to understand coroutines, because Iāve never heard about them before.
[edit: completely overlooked the link in Mathiasā comment , will look into it asap]
My plan was now (based on your feedback), to outsource the generation to a thread. I would save the generated chunks in a module āmapā. Then I would update the Tilemaps depending on the current player position by taking the chunk-information from the āmapā module. The problem is, I canāt seem to manage saving the chunk information into the module because the coroutine is isolated (as far as I understand).
Question 1: How to access Modules from coroutine?
Question 2: If Q1 not possible, how to pass the needed information to the coroutines?
(I would also appreciate links with information about this topics)
I implemented the code this way so far:
local function generate_world(thread_pause)
local x = CC.current_chunk.x
local y = CC.current_chunk.y
print('thread started')
--do world generation
for i = x - 3, x + 3 do
for j = y - 3, x + 3 do
if not MAP[i..'_'..j] then
MAP[i..'_'..j].ground = fractal.noise_chunk(CC.root_seed, i, j, 4, nil, 5)
end
end
if check_pause() then thread_pause() end
end
--second pass
for i = x - 3, x + 3 do
for j = y - 3, x + 3 do
if not MAP[i..'_'..j] then
MAP[i..'_'..j].onground = fractal.noise_chunk(CC.seeds.trees, i, j, 1, 0.013, 4)
end
end
if check_pause() then thread_pause() end
end
--etc
end
I would need the modules:
- MAP - for storing the generated Chunks into it
- CC - for getting the current player position (could possibly move the info into the MAP module)
- fractal - for fractal noise generation
Some notes/clarifications:
- Lua itself is single threaded. So you may well end up with a bottleneck there.
- Coroutines arenāt threads. They help with breaking up code into āstagesā but they are all run on the Lua main thread.
- You cannot update a tilemap from a thread, not even via C++.
Inside a coroutine, you have access to the regular Lua context. I.e. modules you have required etc.
As for your practical bottlenecks, Iād start with timing the various parts and looking at them and checking if the time they take is reasonable. (It is hard for us to say with out examples/bnumbers)
That said, I think reading/generating a chunk of data is totally feasible from C++, and then you can either call a callback (like in my example), or poll for the info, to check when its done.
Iād recommend getting all the data in one call (e.g. a large table), as each Lua <-> C++ call is in itself expensive so youāll want to keep those to a minimum.