love.thread stopping when .newImageData is called?

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
milo_goat
Prole
Posts: 5
Joined: Sun Nov 12, 2023 3:47 pm

love.thread stopping when .newImageData is called?

Post by milo_goat »

im working on trying to multithread my program to load textures in the background, I think that any .graphics function wont work from what ive seen online but im trying to use love.image.newImageData to get the texture then :push it to the main thread to be stored but the code stops after the newImageData() function is called in the threaded script :

main thread code

Code: Select all

local loadingThread = love.thread.newThread("threadedLoading.lua")
local sendChannel = love.thread.getChannel("sendImage")
local recieveChannel = love.thread.getChannel("recieveImage")

function TextureGet:load()
	loadingThread:start()

	direcs = {} -- unimportant but holds all the information about where animations and textures are located
	
	local totalImgs = 0

    	for i, v in ipairs(textureDirecs) do
        	for j = 1, v.len do
            		totalImgs = totalImgs + v[j].count
        	end
	end
	-- counts all textures needed to be loaded (mainly for making sure the number of textures doesnt get too high
	
	local finishedSendingInfo = false
    	local loadedImgs = 0
    	local completionPerc = 0

    	local direcInd = 1
    	local itemInd = 1
    	local countInd = 1

   	local direcTo = #textureDirecs
    	local itemTo = textureDirecs[1].len
    	local countTo = textureDirecs[1][1].count

    	sendChannel:push({textureDirecs[direcInd][itemInd].direc, countInd, textureDirecs[direcInd][itemInd].type1, textureDirecs[direcInd][itemInd].type2}) -- send info before starting loop

	-- while loop doesnt need to be understood, it loops until it recieves the next image then itll send the next info for the next image
	--and waits for that, but problem isnt here, thread gets a :push before the loop starts
    	while not finishedSendingInfo do -- convoluted way of doing a non linear for ( for ( for ())) loop
        	local rept = recieveChannel:pop()

        	if rept then
            		sendChannel:push({textureDirecs[direcInd][itemInd].direc, countInd, textureDirecs[direcInd][itemInd].type1, textureDirecs[direcInd][itemInd].type2})

            		countInd = countInd + 1
            		if countInd > countTo then
                		countInd = 1
                		itemInd = itemInd + 1

                		if itemInd > itemTo then
                    			itemInd = 0
                    			direcInd = direcInd + 1

                    			if direcInd > direcTo then
                        			finishedSendingInfo = true
                        			break
                    			end

                    			itemTo = textureDirecs[direcInd].len
                		end

               			countTo = textureDirecs[direcInd][itemInd].count
            		end
        	end
        	
        	-- draw screen and push to window
        end
end
thread code,
code with problem in it :

Code: Select all

local recieveChannel = love.thread.getChannel("sendImage")
local sendChannel = love.thread.getChannel("recieveImage")

while true do
    local direcInfo = recieveChannel:pop()
    -- {'directory', 'number', 'type1', 'type2'}

    if type(direcInfo) ~= nil then
        print('recieved information') -- when :push is called in main thread this will print

        direcInfo[1] = love.image.newImageData(direcInfo[1] .. direcInfo[2] .. '.png') -- code just stops running? when it reaches this line

        print('information calculated') -- never prints despite the code above it does

        sendChannel:push(direcInfo)
    end
end
direcInfo[1] .. direcInfo[2] .. ".png" does lead to a valid .png file

idk what is happening that could cause this but if there is a fix for this or a different way to load textures in a different thread then i would like to know.
User avatar
BrotSagtMist
Party member
Posts: 614
Joined: Fri Aug 06, 2021 10:30 pm

Re: love.thread stopping when .newImageData is called?

Post by BrotSagtMist »

threadcode has an empty environment, you need to load love.image before you can use it.
I am doing the same, works great, i am using Channel:demand() to control the loop.
obey
User avatar
zorg
Party member
Posts: 3444
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

Re: love.thread stopping when .newImageData is called?

Post by zorg »

As brot said, that's probably the issue; from the wiki:
When a Thread is started, it only loads love.data, love.filesystem, and love.thread module. Every other module has to be loaded with require.
Also, you can get the error from a dead thread i believe, so that would have been helpful to us and you as well, since it would have probably said something about lua lightuserdata vs. a löve ImageData object.
Me and my stuff :3True Neutral Aspirant. Why, yes, i do indeed enjoy sarcastically correcting others when they make the most blatant of spelling mistakes. No bullying or trolling the innocent tho.
RNavega
Party member
Posts: 251
Joined: Sun Aug 16, 2020 1:28 pm

Re: love.thread stopping when .newImageData is called?

Post by RNavega »

milo_goat wrote: Mon Jan 15, 2024 5:06 pm it loops until it recieves the next image then itll send the next info for the next image
--and waits for that, but problem isnt here
This is off-topic, but couldn't you send all direcs at once, with a single :push()?
This way it would simplify the back-and-forth, the loading thread would only need to check if the freshly loaded asset was read by the main thread, so that it could continue loading the next assets in the list. That is, only while a second channel (used to stop the loading thread) doesn't have any messages in it.
User avatar
BrotSagtMist
Party member
Posts: 614
Joined: Fri Aug 06, 2021 10:30 pm

Re: love.thread stopping when .newImageData is called?

Post by BrotSagtMist »

Doing all at once defeats the purpose of doing an asynchronious thread load.
obey
RNavega
Party member
Posts: 251
Joined: Sun Aug 16, 2020 1:28 pm

Re: love.thread stopping when .newImageData is called?

Post by RNavega »

From their code, they already know the entire list of paths to the textures to be (asynchronously) loaded, before the thread is started.

Code: Select all

direcs = {} -- unimportant but holds all the information about where animations and textures are located
My hunch is that sending 'direcs' in a single push, and then letting the thread worry about loading/pushing each asset in sequence, would simplify that loop in the main thread.

Edit: I'm on my phone right now, when I get to a computer I'll see if it's true or not. It's difficult to write code with a virtual keyboard heh
User avatar
BrotSagtMist
Party member
Posts: 614
Joined: Fri Aug 06, 2021 10:30 pm

Re: love.thread stopping when .newImageData is called?

Post by BrotSagtMist »

Thing aint even a 10 liner, how do you want to simplify it?
obey
RNavega
Party member
Posts: 251
Joined: Sun Aug 16, 2020 1:28 pm

Re: love.thread stopping when .newImageData is called?

Post by RNavega »

Not simplifying in absolute code lines, you're simplifying the relationship between the two threads. In the original, the main thread is both consumer and producer, and the child thread is also both consumer and producer.
Since you already know the metadata for all assets to be loaded in the child thread before it's even started, you can simplify that relationship by sending all of it (including limits, restrictions etc) to the child thread, so as to make the main thread only a consumer and the child thread only a producer -- not completely though, you still need some way for the main thread to signal for the child thread to abort, like if the user wants to quit the game while the threaded loading is happening.

If you add error handling with pcall() and other checks to gracefully handle failures, this will lead to more than 10 lines.
I'm okay with that.
More error handling/messaging might also tell why the OP's newImageData() call is failing.

This example prints to the system console:

Code: Select all

io.stdout:setvbuf('no')


local inputData = {
    {path = 'assets/textures/enemy01.png',           param1 =  66, param2 = 1},
    {path = 'assets/textures/enemy02.png',           param1 =  13, param2 = 2},
    {path = 'assets/textures/decor_lantern01.png',   param1 =   2, param2 = 3},
    {path = 'assets/textures/pistol.png',            param1 =  11, param2 = 4},
    {path = 'assets/textures/decor_portal_open.png', param1 = 105, param2 = 5},
    {path = 'assets/textures/decor_tapestry04.png',  param1 =   7, param2 = 6},
    -- Make an invalid entry so the loader thread fails on purpose.
    {path = 'assets/textures/player01.png',          param1 = nil, param2 = nil},
}

local loaderThread
local loaderStopChannel
local loaderChannel

local loaderThreadCode = [[
require('love.timer')

local stopChannel = love.thread.getChannel('loaderStopChannel')
local channel = love.thread.getChannel('loaderChannel')

local function pushResult(type, value)
    channel:push({type=type, value=value})
end

local function canContinue()
    local flag = stopChannel:getCount() == 0
    if not flag then
        pushResult('debug', '(Forced stop)')
    end
    return flag
end

local function main(inputData)
    pushResult('debug', 'Loader thread -> main() enter')
    local currentIndex = 0
    while canContinue() do
        currentIndex = currentIndex + 1
        local inputEntry = inputData[currentIndex]
        if inputEntry then
            if inputEntry.param1 and inputEntry.param2 then
                -- Do something special with the input data entry.
                -- Push the result into the loader thread channel.
                -- (...)
                love.timer.sleep(0.75)
                local textureAsset = {texture=nil, width=256, height=128, path=inputEntry.path}
                pushResult('texture', textureAsset)
            else
                error('Invalid asset entry on index '..currentIndex)
            end
        else
            -- Exhausted the input data, exit the loop.
            break
        end
    end
    pushResult('debug', 'Loader thread -> main() exit')
end

local inputData = select(1, ...)
if inputData then
    local status, message = pcall(main, inputData)
    if status then
        return pushResult('debug', 'Loader thread -> finished')
    else
        return pushResult('error', message)        
    end
else
    return pushResult('error', 'No input data sent in through :start()')
end
]]


local function startLoaderThread(inputData)
    loaderChannel:clear()
    loaderThread = love.thread.newThread(loaderThreadCode)
    loaderThread:start(inputData)
end


local function tryStopLoaderThread()
    if loaderThread then
        loaderStopChannel:push(true)
        loaderChannel:clear()
    end
    loaderThread = nil
end


function love.update(dt)
    if loaderChannel:getCount() > 0 then
        local result = loaderChannel:pop()
        if result then
            if result.type == 'debug' then
                print('Debug:', result.value)
            elseif result.type == 'texture' then
                local textureAsset = result.value
                print('Texture:', textureAsset.path, textureAsset.width, textureAsset.height)
            elseif result.type == 'error' then
                print('Error:', result.value)
                tryStopLoaderThread()
            end
        end
    end
    -- Update animations, react to user input etc. while the loader thread
    -- is still working in the background.
end


function love.load()
    loaderStopChannel = love.thread.getChannel('loaderStopChannel')
    loaderChannel     = love.thread.getChannel('loaderChannel')
    print('Press any key to start the simulated "threaded loading".')
    print('Press Esc to stop the loading, or quit.')
    print()
end


function love.keypressed(key)
    if key == 'escape' then
        if loaderThread then
            tryStopLoaderThread()
        else
            love.event.quit()
        end
    elseif not loaderThread then
        startLoaderThread(inputData)
    end
end
Post Reply

Who is online

Users browsing this forum: No registered users and 75 guests