OTOH, I would use a very different method. I would use a thread pool
to store threads allocated to running scripts. You do not need a
thread for each script. No matter how many threads you have you will
never have more threads running than you have processors. So, you can
limit your self to one thread per processor and save some overhead.
(You might need more if you have other blocking operations in your
scripting system.) Put your threads in a thread pool and share them
between scripts. You can implement wait() by having it place a script
in a priority queue (a sorted queue aka a heap). Its priority is the
time when it is supposed to wake up. A wait(0) should (if your queue
is correctly implemented) put a script at the end of the group of
threads already scheduled for the same time so it will just give up
control to the next ready to run script.
If no script is ready to run, use a timer to wake up a thread at the
time a script will be ready to run. If more than one script is ready
to run let more threads out of the pool and have them run the other
ready scripts. When a thread finishes it should check to see if there
are other ready scripts and run them. If there are no ready scripts it
should go back to the pool. If it is the last active scripting thread
it should wait on a timer for the next waiting thread. Or, if the
queue is empty wait on the queue so it will awaken when a script is
placed in the queue. If you want to get fancy you can have the threads
do fine grain scheduling (time slicing) on the ready scripts. Which is
something your won’t necessarily get out of the OS.
Interesting idea. I had something very similar to your thread pool, though
kind of different. Delphi has a TThread class that encapsulates basic
thread functionality, and I created a TScriptThread subclass, (all custom
types in Delphi start with T by convention,) that had all the logic for
executing scripts. I had my project’s TScriptEngine object allocate an
array of TScriptThread objects and keep track of which ones were
currently in use. When a new script came to the script engine, it would
pass it to the next available script thread and set it to run, and when the
script thread finished running, it had a callback to the script engine to
tell it it was ready to accept another script.
I originally did a naive wait implementation, having it simply call
Sleep(howeverlong), but that caused some trouble with getting script
threads to end if you wanted to terminate one quickly. TThread has a
Terminated property that you can set from outside the thread, but it leaves
it up to you to implement it. Your thread’s main loop is supposed to check
for Terminated and if it’s set to true, clean up and gracefully exit. Sleeping
like that made it impossible to terminate efficiently, and waiting on a timer
would have caused similar problems.
Thankfully, the script interpreter has an event handler called "OnRunLine"
that can invoke a callback after each line in your script. I created a
simple custom timer class that only had two functions: a constructor that
took a value in miliseconds, and a function to return the time remaining, or
0 if the time was up. Its logic was simple enough that the time to run these
calculations was negligible. So I added one of these timers to the TEventThread
class, and had the Wait command set it. Then the OnRunLine event handler
would check a few things after each line of code in the script had run: if
Terminated was set, it told the interpreter to clean up and quit. If the delay
timer was assigned, it would check the time remaining, compare against the
length of one game frame, and sleep for whichever was less. It ran these two
checks in a loop until either Terminated was set or the time was up.
Not sure whether it was Windows, Delphi, or just dumb luck, but I never ran
into any problems with script threads blocking the main thread or scheduling
themselves in its way so it stopped rendering. I kept my TScriptThread array
to a reasonable size (somewhere around 16 IIRC) and never had any trouble with
it until I ran into a few horrible projects that were trying to run 50 scripts at once.
That brought my script engine to its knees, of course. (The existence of these
projects is a result of poor design in the original system I built my engine to
emulate. There were no event handlers, which left no way to do certain simple
tasks other than infinite loops in global-scope scripts, and some people had
a whole lot of them. I’m currently redesigning my framework to support event
handlers, and when it’s done I’ll have the script importer scan for these ugly
global scripts, analyze them, and assign them to events instead.)
Lets take on the problem of sending commands to the rendering thread.
This is a common problem in threaded code. You need to ask another
thread to do some work for you, and you can’t continue until it is
done. The easiest, and safest, way that I know of to solve the problem
is based on a queue and a semaphore.
I basically just built this into the OnRunLine loop I mentioned above.
Instead of waiting on semaphores, I had any script event that required
an operation over time from the map engine register a callback function
that checked the state of the engine and returned a boolean value. If it
wasn’t done, it would sleep for another frame.
If you actually force your threads to sleep for longer than 0 you may
be wasting processor time that you could have used.
That wasn’t an issue here. Sleeping doesn’t waste time, it hands it off
to other threads or other processes, and my basic functional time unit
was the frame. My testing indicated that when Windows switched away
Each frame was about 33 ms long, and the main thread’s loop took about
12 ms to process each frame and ship it off to the GPU to render, under
the heaviest load I could place on it. (Lots of sprites on-screen at once,
animations, rotations, full-screen transitions, color overlays, the whole nine
yards.) Add in a Windows process cyle and we have 28 ms, leaving a
minimum 5 ms per frame for the script engine to run one or more lines of
each active script.
Each command in the API was designed to return in a negligible amount
of time. No calculation-heavy loops or anything like that. It would check
something, maybe change something, then report back to the interpreter
within a handful of cycles. Anything that required a non-negligible amount
of time would instruct the main thread to take care of it, then either activate
the timer or register a state-checking callback in the OnRunLine event
handler, then sleep. And since Windows would get back to it within 16 ms,
there was no need to worry about sleeping through the next frame.
It was a bit of a balancing act, but this was back before I knew about
semaphores, and the system was simple enough that I really didn’t need
them. Nothing ever broke, except for when some projects tried to run more
scripts than the engine was designed for, and that can be fixed with an
improved design of the scripting system. I have a few tricks planned out that
should ensure that there are never more than 10 scripts trying to run at a time,
tops. There really shouldn’t be more than 5 under normal use.
----- Original Message -----
From: email@example.com (Bob Pendleton)
Subject: Re: [SDL] SDL_GL_SwapBuffer() + vsync randomly causes high CPU usage–or not…
from my program, it generally got back around to it about 16 ms later.