Basic Callback Thread

Discuss the development of new homebrew software, tools and libraries.

Moderators: cheriff, TyRaNiD

Post Reply
Vincent_M
Posts: 73
Joined: Tue Apr 03, 2007 4:16 am

Basic Callback Thread

Post by Vincent_M »

Alright, I've gotta say, it's pretty embarrasing to have to ask this question, but: how do the basic callbacks work? Here's some sample code to illustrate my example:

Code: Select all


static int running = 1;
/* Exit callback */ 
int exit_callback(int arg1, int arg2, void *common) 
{
   running = 0;
   sceKernelExitGame(); 

   return 0; 
} 

/* Callback thread */ 
int CallbackThread(SceSize args, void *argp) 
{ 
   int cbid; 

   cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL); 
   sceKernelRegisterExitCallback(cbid); 

   sceKernelSleepThreadCB(); 

   return 0; 
} 

/* Sets up the callback thread and returns its thread id */ 
int SetupCallbacks(void) 
{ 
   int thid = 0; 

   thid = sceKernelCreateThread("update_thread", CallbackThread, 0x11, 0xFA0, 0, 0); 
   if(thid >= 0) 
   { 
      sceKernelStartThread(thid, 0, 0); 
   } 

   return thid; 
}

int main()
{
     // setup the callbacks
     SetupCallbacks();

     while(running) {
          // main loop
     }

     sceKernelSleepThread(); 
     return 0;
}
I know it all starts in main()... That was the easy part (lol sorta). Then, I looked at SetupCallbacks(). What I know it does is creates a thread called "update_thread", and calls the functions, CallbackThread(), and is set at a high priority. The creation of the thread will return an integar that is stored in 'thid', which is used to start the thread if it's number is greater than -1. I'm guessing that means that "if the thread was created successfully, then run the thread" if that 'if' statement.

I get that part, but then, things start to get trickier. The function, CallbackThread(), gets called as soon as "update_thread" is started, right? Also, does "update_thread" keeping calling Callback thread until it is put to sleep/terminated?

Then, once CallbackThread() is called, it creates a callback. Is a callback a thread? What are the differences exactly between callbacks (I'm assuming it's a type of thread) and regular threads such as update_thread? Then, it registers that callback thread to be used as an exit callback thread. All I know is that it is needed for the Home button to successfullly shut down the application. The last thing CallbackThread() does is puts the exit callback to sleep, right?

The exit callback thread function, exit_callback is called when the exit callback function is ran, right? What it does is call sceKernelExitGame(). What does sceKernelExitGame() do? Actually, when does exit_callback get called? I'm sure it has to be able to be called when inside the main loop because then running can be set to '0', terminating the loop. The idea of running was Insert_Witty_Name's advice, so I'm not sure how it works. I think exit_callback is called when 'Yes' is selected when the Home button is pressed, bringing up the exit game screen. The exit_callback does that because it was registered to do that with sceKernelRegisterExitCallback() back in CallbackThread(). After the main loop, sceKernelSleepThread() is called. That will put update_thread to sleep, right?

One other thing I was wondering is, what are the purposes of the arguments in the callback functions? CallbackThread() has two, and exit_callback has three. They look similar to the ones in main(), but I don't see what the point of them are. Maybe to be used similarly like main()'s arguments? They can be used for commandline-based debugging and such, right?
Smong
Posts: 82
Joined: Tue Sep 04, 2007 4:44 am

Post by Smong »

I'm not pretending to know all the answers, anyone please correct any mistakes I've made in this post.
The creation of the thread will return an integar that is stored in 'thid', which is used to start the thread if it's number is greater than -1. I'm guessing that means that "if the thread was created successfully, then run the thread" if that 'if' statement.
Yes, the thread ID can also be used for other things, check the documentation (I usually type the function name followed by jim.sh into google).
does "update_thread" keeping calling Callback thread until it is put to sleep/terminated?
No, "update_thread" and CallbackThread are the same things, one is just the name of the thread, the other is the function that is called when the thread starts. The function is called each time sceKernelStartThread is called, this means it's possible to create 2 different threads that use the same thread function.
Is a callback a thread?
No. Callbacks are functions that get called from "other places" (another thread or an interrupt). Callbacks can get called at any time (like the user pressing the Home button) or regularly (like a timer interrupt). Threads while only called once to start them, usually contain loops so they don't reach the end of the function and return. The operating system will keep switching between all the running threads giving the impression that things are happening in parallel. This is how running can be set to 0 in one thread and another thread sees that it has changed and takes appropriate action. Note sharing variables between threads can be "dangerous" (race conditions), attempting to fix this can be even worse (deadlocks), if you want to use threads in your own programs you should really read some articles about it.
The last thing CallbackThread() does is puts the exit callback to sleep, right?
Kind of, it puts the thread to sleep, but the CB part in the name of the sleep function means it will wakeup if any callbacks need to be called.
After the main loop, sceKernelSleepThread() is called. That will put update_thread to sleep, right?
No it will put the current thread to sleep. There are now 2 threads running, the "main thread" and the "update_thread".
One other thing I was wondering is, what are the purposes of the arguments in the callback functions? CallbackThread() ... Maybe to be used similarly like main()'s arguments? They can be used for commandline-based debugging and such, right?
Yes they are similar to main()'s arguments. You can set them in sceKernelCreateCallback and sceKernelCreateThread (again check the docs). They can be used for all kinds of things.

Finally setting running to 0 and calling sceKernelExitGame() in the callback isn't a good idea. sceKernelExitGame() will immediately exit your entire program. Instead you should remove sceKernelExitGame from the callback and replace the sceKernelSleepThread in main() with sceKernelExitGame. This way you can add code to the end of main to perform any necessary cleanup before exiting (such as closing open files, disconnecting from a server, etc).
(+[__]%)
Vincent_M
Posts: 73
Joined: Tue Apr 03, 2007 4:16 am

Post by Vincent_M »

Ok, so main() does create a thread. I thought that update_thread was the main thread, but that's just so that I can check to see if the Home button was pressed so I can run my exit callback, right? I understand that sceKernelSleepThread() will put the current thread to sleep, so would it put update_thread() to sleep? Ok, so a callback is the function that is called to run under a thread. I think I'm getting it.
Hellcat
Posts: 83
Joined: Wed Jan 24, 2007 2:52 pm

Post by Hellcat »

Hello everyone :)
Sorry, for digging up this fairly older thread, but since I just had to use callbacks and I couldn't find samples for setting up own callbacks (beside the exit_game one) I took the liberty of screwing together a small sample/skelleton for that and found this a good place to post it.

Here we go, setting up own callbacks that can be "called" throughout the whole system....

Setting them up:

Code: Select all

int myCallbackID1;
int myCallbackID2;

int myCallbackNumberOne(int arg1, int arg2, void *arg)
{
  // arg2 will be passed to us when this callback is called by someone
  
  /*
    put some code in here that does something
  */
  
  return 0;
}

int myCallbackNumberTwo(int arg1, int arg2, void *arg)
{
  /*
    put some code in here that does something
  */
  
  return 0;
}

void SetupMyCallbacks(void)
{
  // this function will be ran as seperate thread
  // the purpose of this thread will be to set up all callbacks
  // and then go to sleep to allow other threads to call
  // the callbacks we did set up here
  // the callbacks will be sort of "made available" off this thread
  
  myCallbackID1 = sceKernelCreateCallback("FirstCallback", myCallbackNumberOne, NULL);
  myCallbackID2 = sceKernelCreateCallback("AnotherCallback", myCallbackNumberTwo, NULL);
  
  // put this thread to sleep and keep serving the callbacks
  sceKernelSleepThreadCB();
}

int main(int argc, char *argv[])
{
  int thid;
  // some code here
  
  // let's create the thread that will set up and serve our callbacks:
  thid = sceKernelCreateThread("CallbackThread", SetupMyCallbacks, 0x11, 0xFA0, 0, 0);
  if (thid >= 0)
  {
    sceKernelStartThread(thid, 0, 0);
  }
  
  /*
    We now have the IDs of our callbacks in myCallbackID1 and myCallbackID2.
    Those IDs can be passed to everyone who might want to "call us back" ;-)
    
    Something like
    OtherPRXFunctionToTakeOurCallbackIDs(myCallbackID1, myCallbackID2);
  */
  
  // some code here
  
  return 0;
}

"Calling back" (this code can be anywhere, another .PRX for example):

Code: Select all

int cbID1;
int cbID2;

void OtherPRXFunctionToTakeOurCallbackIDs(int cb1, int cb2)
{
  // remember the passed callback IDs for later usage
  cbID1 = cb1;
  cbID2 = cb2;
}

void SomeRandomFunctionThatDoesSomething(void)
{
  // some code here
  
  // let's assume we want to call our second callback at this point
  // because we the other .PRX to do whatever it does there
  // (the "42" in this example is passed to the callback function as arg2)
  sceKernelNotifyCallback(cbID2, 42);
  
  // some code here
}

I hope I didn't put in any mistakes, this has been mostly typed from memory....
Maybe someone might find this usefull, like said, that was exactely the setup I needed to let .PRX #1 do something triggered by .PRX #2 without setting up a loop that constantly checked if there's something to do.


:)
gauri
Posts: 35
Joined: Sun Jan 20, 2008 11:17 pm
Location: Belarus

Post by gauri »

Okay, and why then not this way:

Code: Select all

static int running = 1; 
/* Exit callback */ 
int exit_callback(int arg1, int arg2, void *common) 
{ 
   running = 0; 
   sceKernelExitGame(); 

   return 0; 
} 

int SetupCallbacks(void) 
{ 
   int cbid; 

   cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL); 
   sceKernelRegisterExitCallback(cbid); 
   return 0;
} 

int main() 
{ 
     // setup the callbacks 
     SetupCallbacks(); 

     while(running) { 
          // main loop 
     } 

     sceKernelSleepThread(); 
     return 0; 
}
I mean, what's the big reason that callbacks are created in the separate thread and the thread then is suspended?
Freelance game industry veteran. 8]
Arwin
Posts: 426
Joined: Tue Jul 12, 2005 7:00 pm

Post by Arwin »

Really stupid question probably, but ... is sceKernelCreateCallback user mode or kernel mode, and if kernel mode, is there a user mode equivalent?
J.F.
Posts: 2906
Joined: Sun Feb 22, 2004 11:41 am

Post by J.F. »

It's callable from usermode. You'll notice virtually all programs use it, even the 3.xx usermode apps.
Arwin
Posts: 426
Joined: Tue Jul 12, 2005 7:00 pm

Post by Arwin »

Ok, great. I'm trying to get some of my old projects to work on 5.0 M33-06 without installing the 1.50 firmware, but so far I'm finding it pretty difficult. Was worried that this was holding me back. The samples included in the sdk all still seem to be aimed at 1.5 and date back as far as 2005.
J.F.
Posts: 2906
Joined: Sun Feb 22, 2004 11:41 am

Post by J.F. »

There are threads covering converting from 1.50 to 3.xx homebrew, but the basics are:

1 - Use usermode. You set that in the module header in the main file.
2 - Make it a prx. You set that in the makefile.
3 - Set the heap size. If you make the app a prx, the heap size must be set explicitly. Do that in the main file.
4 - If you want the extra memory, set a flag in the makefile, and make the heap size value negative.

The module header in the main file would look like this:

Code: Select all

PSP_MODULE_INFO("BasiliskII", 0, PSP_VERS, PSP_REVS);
PSP_MAIN_THREAD_ATTR(PSP_THREAD_ATTR_USER);
PSP_HEAP_SIZE_KB(-256);
And the main changes to the makefile look like this:

Code: Select all

BUILD_PRX = 1
PSP_FW_VERSION = 500
PSP_LARGE_MEMORY = 1
The PSP_FW_VERSION really isn't important.

Q&A

1 - Q: Why make the app usermode? A: Kernel mode apps require 1.50, which is not available on the Slim unless you use the Time Machine or eLoader/LEDA, which doesn't work on all 1.50-based apps.

2 - Q: Why make it a PRX? A: You need to remove all kernel mode calls from the app and move them into an external PRX you load in the program. Kernel mode PRXs can only be loaded by an app if it's also a PRX. If your app never used any kernel mode calls and you don't plan to load any kernel mode PRXs, you don't have to make the app a PRX.

3 - Q: Why is the heap size negative? A: That was a change in the SDK made a while back to make it possible to use one executable for both the Phat and Slim with large memory set. A negative heap size means reserve all memory EXCEPT for the number of KB specified by the negative value. So -256 means make a heap equal to the maximum amount of memory leaving 256 KB free. Without the negtive heap size, you'd have to compile two different apps with two different absolute heap sizes for the Phat and the Slim (or just one and ignore the extra memory on the Slim).

4 - Q: Why leave any memory? Why not just use ALL the memory? A: A certain amount of memory must be left free for things like the stacks for threads and for PRXs that are loaded by the app. For example, using all the memory for the heap will make thread creation fail, which may cause the app to fail (depending on what happens in the app when threads can't be created).
Arwin
Posts: 426
Joined: Tue Jul 12, 2005 7:00 pm

Post by Arwin »

Ok, super, that's really helpful. I had figured out a few things myself and managed to get one or two things to work, but not everything. Basically I now managed to compile a prx just once by accident, and otherwise am now up and running again using Heimdall's environment. I used all the information I could find in the sticky thread.

Unfortunately, I can only get reliable results compiling FW1.5 stuff so far it seems, in that I have to put the EBOOTs into the GAME150 folder. But it may also have to do something with the psp-gcc and eclipse at this point - it almost seems that only every other try I get a proper compile, without changing any code in the meantime, and clean project command doesn't seem to work properly so I use my own clean.bat.

Do you need to do something to the pspsdk to make it compile 3.xx stuff? I've been out of the running for three years, so I have some catching up to do. :D But at least I managed to implement some good ideas by RyCatcher and make the p-sprint stuff a lot more useable already!

Thanks loads for your help so far.
J.F.
Posts: 2906
Joined: Sun Feb 22, 2004 11:41 am

Post by J.F. »

No, nothing specific is needed if you're using Heimdall's pre-done toolchain.

Be sure to put the eboot in GAME or GAME5XX, not GAME150. If in GAME and you have the 1.50 add-on (for the Phat), be sure to set GAME to 3.xx mode in the recovery menu.
Post Reply