Hello, I read about SDL_ShowOpenFileDialog and I saw the following piece of information about the callback you pass to it:
“Note that the callback may be called from a different thread than the one the function was invoked on.”
The windows builds of my program can’t seem to load from this callback, the documentation tells me that is a possibility, but doesn’t tell me what to do about it, as far as I can see, which has been a very confusing experience to me as a hobbyist dev.
Does SDL have any built-in way to direct the file-loading management, or is the way to go about this to simply queue my assets somewhere in my program? Any directions? https://wiki.libsdl.org/SDL3/SDL_ShowOpenFolderDialog
I think you meant to link to SDL_ShowOpenFileDialog instead of the folder one.
I’ve only used the file dialog/browser a couple of times so far, so this may not be how everyone does it:
For my own purposes, I don’t try to do too much in the callback itself, I copy the list into a user event (data1) and push that so that I can catch it in the main event loop. This must be a deep copy.
I want to load these files in the main thread because many SDL functions hold that as a requirement.
In the callback it does make sense to check that the files provided are actually file-types you want to open, but try to keep the callback trim when possible.
Here’s some working example of getting those strings into main, written in C++ because it reduces the amount of allocations I have to manage. Press escape to exit, any other key to open the file browser.
#include <SDL3/SDL.h>
#include <vector>
#include <string>
SDL_Event readFiles;
// I'm not going to deal with filters, check out SDL/test/testdialog.c to see that in action
// the goal is to get the fileList back to main loop
void callback(void * userData, const char * const * fileList, int filter)
{
if(fileList)
{
if(fileList[0])
{
std::vector <std::string> * files = new std::vector <std::string>;
while(*fileList)
{
if(*fileList)
{
files->push_back(*fileList);
SDL_Log("File added to list: %s", *fileList);
}
fileList ++;
}
readFiles.user.data1 = files;
SDL_PushEvent(&readFiles);
}
}
}
int main()
{
SDL_Window * window = SDL_CreateWindow("File Dialog", 400, 400, SDL_WINDOW_RESIZABLE);
SDL_Renderer * renderer = SDL_CreateRenderer(window, 0);
SDL_SetRenderVSync(renderer, 1);
SDL_zero(readFiles);
const int MY_EVENT_READ_FILES = SDL_RegisterEvents(1);
readFiles.user.type = MY_EVENT_READ_FILES;
SDL_Event ev;
bool run = true;
while(run)
{
while(SDL_PollEvent(&ev))
{
switch(ev.type)
{
case SDL_EVENT_USER:
if(ev.user.type == MY_EVENT_READ_FILES)
{
SDL_Log("These are filepaths as read from main thread");
if(ev.user.data1)
{
std::vector <std::string> * data = (std::vector <std::string> *) ev.user.data1;
for(size_t i = 0; i < data->size(); i ++)
{
SDL_Log("%s", (*data)[i].c_str());
}
delete data;
}
}
break;
case SDL_EVENT_KEY_DOWN:
switch(ev.key.key)
{
case SDLK_ESCAPE:
run = false;
break;
default:
// would be easier to only allow one file if loading a game save.
SDL_ShowOpenFileDialog(callback, NULL, window, NULL, 0, NULL, true);
SDL_Log("The problem is that this is non-blocking, we don't know when to read the files vector without pushing an event");
break;
}
break;
case SDL_EVENT_QUIT:
run = false;
break;
}
}
SDL_RenderClear(renderer);
SDL_RenderPresent(renderer);
}
SDL_Quit();
}
This will work if you use SDL_PollEvent. If you instead use the main callbacks then SDL_AppEvent will get called on the same thread as you pushed the event from.
https://wiki.libsdl.org/SDL3/SDL_AppEvent#remarks
There is (currently) no guarantee about what thread this will be called from; whatever thread pushes an event onto SDL’s queue will trigger this function.
Dang, I thought I had a fool-proof option there.
I still think it’s important to get the file paths to the main thread to load textures and such. Do you have any clues or tricks so that can be done while using the main callbacks API?
I’m out of my depth on mutexes, could they be used to sync to main in this situation?
Use a global* list of file names. Add the names in the callback. In the main thread, check the list every frame and load the files that are there, and make sure the list is empty afterwards. Use a mutex to prevent multiple threads from accessing the list at the same time.
* The list doesn’t necessarily have to be global but it needs to be accessible by both the callback and the main thread (same for the mutex).
Yes, the list and the mutex could be passed to the callback using the userdata parameter. They don’t need to be globals but they need to be scoped so that they are accessible where you want to use them in the main thread and so that they are still alive when the callback is called.