sourcepawn
                                
                                 sourcepawn copied to clipboard
                                
                                    sourcepawn copied to clipboard
                            
                            
                            
                        [future] Fiber/Userspace Threads
I'm sure this was probably suggested by someone else a while back but I was wondering if there's any consensus on adding fibers/userspace threading as part of the SM standard library?
I'll try to answer this in terms of SourceMod, since that's what drives SourcePawn development.
Threads: maybe, but it's a huge can of worms. No one is going to start defining an atomicity model for SourcePawn and then rewriting the VM around it. So if we ever add thread support, it will be in one of two ways: (1) loading separate plugins with completely separate address spaces, or (2) allowing "async" functions, but denying them access to shared global state. In both models you'd communicate with threads via message passing. Additionally, we'd have to enforce that only "thread-safe" natives can be called off the main thread. You can't be allowed to call IsClientConnected() off the main thread, for example. Auditing everything is an enormous task, and some natives have implicit engine access (FormatString + "%n" for example).
Fibers: It's not clear what the use case would be or what advantages they would have in SourceMod. You really don't want to be fiber-switching the game thread.
Coroutines/generators: Yes, we could do this after closure support.
It'd also help to understand what people want to do with threads in SourceMod, and we can figure out how to best accommodate those use cases.
Idk about what other devs would do with fibers/threading from SM but running code that's done a per-player basis is one such case. From VSH2, perhaps the API can look like creating a new Fiber as a handle taking a function pointer and variadic args:
player = BaseBoss(i);
if( player.bIsBoss ) {
	new Fiber(ManageBossThink, player);
} else {
	new Fiber(ManageFighterThink, player);
}
Again, you'd be limited to data structures and native calls that have no main thread interaction. So if your goal is to off-load main thread work, fibers serve no purpose. If you want to delay main thread work to an idle frame, you can already do that.
The reason for no main thread interaction is that it's not safe to swap the context of the main thread and then start poking at it. It might be safe at the top of the stack, but then we're not doing anything different from CreateTimer.
Again, you'd be limited to data structures and native calls that have no main thread interaction. So if your goal is to off-load main thread work, fibers serve no purpose. If you want to delay main thread work to an idle frame, you can already do that.
RequestFrame correct?
That gets you the very next frame, but I guess that wouldn't tell you anything about how "busy" the frame is. It'd be enough to divvy up work though, so you don't do too much in one frame.
Another problem with the fiber model is that, because the main thread is so stateful, if you switch out of it and then switch back, you have to re-validate all your local state. You already have that problem with timers/sql queries, eg, you have to pass userids and not client indices, because they might disconnect before the timer fires. Fibers would have this problem too, except the control flow would be implicit and way more susceptible to state bugs.
So: fibers, no, threads, yes but we need to think about it really carefully and look at some use cases.
ahh. The reason I was thinking fibers were because they're alot lighter in terms of overhead compared to threads + cooperative multitasking.
How would coroutines work compared to using fibers?
Right now my idea/hack of using "coroutines" is recursively calling RequestFrame in a function.
Coroutines would be nice to have, and they're not that magical (they're just a closure + state machine). You would get the same global state problems as fibers though. Eg, let's say your code looks like this (pseudo-code):
public void OnWhatever(int userid) {
    RequestFrame(function () {
         int client = GetClientFromUserid(userid);
         if (!client) return;
         DoStuff(client);
         RequestFrame(function () {
             int client = GetClientOfUserid(userid);
             if (!client) return;
             DoMoreStuff(client);
         }
    }
}
The recursive nature is clearly pretty gross. But it does make the control-flow very explicit. If we converted that to a coroutine syntax:
public void OnWhatever(int userid) {
    RequestFrame(function () {
         int client = GetClientFromUserid(userid);
         if (!client) return;
         DoStuff(client);
         yield;
         DoMoreStuff(client);
    }
}
This code looks totally reasonable, but it has a subtle bug: "client" is cached state that could be invalid after the yield. It needs to be:
public void OnWhatever(int userid) {
    RequestFrame(function () {
         int client = GetClientFromUserid(userid);
         if (!client) return;
         DoStuff(client);
         yield;
         client = GetClientFromUserid(userid);
         if (!client) return;
         DoMoreStuff(client);
    }
}
And... that doesn't really look that better than the recursive version, does it? Especially if the recursive function was split out into clearly separate functions. So I feel like coroutines are not the best paradigm for this. I'm open to hearing more about it though, and certainly looking at what other scriptable game engines do. I don't have much experience in this area.
I would like to be able to call FindEntityByClassname on a new thread and return how many there are to the main thread. When the thread is done, I would like a callback to the main thread, so I can call ShowSyncHudText for all the clients.
I would like to be able to call
FindEntityByClassnameon a new thread and return how many there are to the main thread. When the thread is done, I would like a callback to the main thread, so I can callShowSyncHudTextfor all the clients.
I don't think FEBC is thread safe, so no amount of threading features in SP will make it work without random failures.