Are proxies handled as seperate threads

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 :confused:

1 Like

Hi @j0-ji !
No the proxies aren’t updated on a separate thread.

Is there anything specific you are trying to do?

1 Like

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 ^^’]

1 Like

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.

2 Likes

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.

6 Likes

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 :man_facepalming:, 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.

5 Likes