Talon: A WoW Threads Library
Programmer’s Guide To Addon Development Using Asynchronous Multithreading
Abstract
This document is a programmer’s guide for Talon, an asynchronous, non-preemptive thread library for World of Warcraft Addon Developers. The guide describes some basic threading concepts and definitions and then presents each of the library’s public API entry points.
Preface
This work was motivated by a number of factors. First, and most immediate, was to find a way to fix a problem when trying to add floating combat text to one of my [personal] addons. I needed a more flexible way to pause execution between lines of text.
Sadly, while threading my application worked fine, its functioned was no better than the conventional approach.
Second, most of my professional software career was spent designing multithread libraries and multithreaded applications. So, I thought, how hard can this be? So, I sank a month and half into the project, found that it wasn’t particularly difficult, and am happy with its functionality. But, and I can’t say this strongly enough, I’m not convinced threading brings much to the developers’ tables. It’s sexy. It’s cool to see it work. But does threading make for better addons? I think in some narrow cases it may, but for general purposes, no.
Third, and I mention this below in the body of this document, there seems to be a lot of confusion about threads. My hope is that this docment will clear up some of that confusion.
Finally, I have been hesitant about contributing this library to the Blizzard addon development community for one reason. The Blizzard client is a beautifully written and elegant event dispatcher. Its design center is one based on the asynchronous processing of events. It’s highly unlikely that adding asynchronous multithreading to an addon will bring much benefit to WoW addon development. But, I hope you’ll give it whirl and maybe discover some capabilities for which WoW threads can be of help.
Michael Peterson, Missoula, MT 2021
Contents
Asynchronous vs. Synchronous 8
Asynchronously Scheduled, Preemptive Threads 8
Asynchronously scheduled, non-preemptive threads: 9
Creating WoW Multithreaded Addons 11
The WoW Multithread Library API 12
Data Structures Error! Bookmark not defined.
Signaling – Thread-to-Thread Communication 16
The Thread Control Block (aka Dispatch Table) 17
Get A Thread’s Unique Identifier 22
Get a Return Value From another thread’s Function 23
Send a Signal To Another Thread 25
Passing Multiple Parameters 27
Introduction
Every so often a post will appear on one of the WoW Forums suggesting that Blizzard would be better off if their program development philosophy would just get with the times and redesign the WoW client to support multithreading.
This would not be wise for all kinds of reasons, but what strikes me when this issue arises is the misconceptions many people have about threads and what are they good for.
This document is a programmer’s guide. It is focused on helping developers determine whether multithreading is a good fit for their addon (HINT: if your current addon is running and serving its clients well, then leave well-enough alone.). Secondarily, I hope to minimize the confusion about threads. But first just a little bit about me.
About the Author
I began playing World of Warcraft shortly after its first release (Vanilla) and have leveled at least 1 character to max in every expansion since.
I hold a PhD in a field completely unrelated to programming (immunology) and as such confers absolutely no credibility insofar as programming is concerned. On the other hand, I am a retired software architect with the following experience:
- Architect and Lead developer of Digital Equipment Corporation’s distributed UNIX-based off-line storage facility (Backup/Restore, Disaster Recovery, etc.,)
- Lead developer and architect for the disaster recovery facility[1] in the Windows NT operating system kernel.
- Wrote the first POSIX threads implementation for Linux[2].
- Author of DCE: A Guide To Developing Portable Applications[3]. This book evolved, in part, from my association with the development of Digital’s Common Multithread Architecture (CMA). CMA, and especially its lead developer, David Butenof, was a formative influence on the development of POSIX threads.
One of the reasons I wrote a threads library for WoW addon development arises from what I believe is a good deal of confusion about threads – what they are and their good, bad, and ugly aspects. So, let’s dive into these muddy waters and see if we can get a clearer understanding along with some perspective as to how multithreading a WoW addon might be just the thing your addon needs, but probably isn’t.
Threading Models
Threads are widely misunderstood and I’d like first to clarify not only what threads are, but what they are best used for.
A thread of execution is defined and described as an executable object able to be independently scheduled. Note that independent and concurrent are very different (more on this later), but independence is the defining characteristic of a thread. To be independent means that a thread’s execution (i.e., when it starts or resumes) is scheduled independent of other threads. Contrast this with, say, coroutine threads. Coroutines are threads which schedule, and are scheduled by, other threads. Without independence, threads are little more that [synchronous] procedure calls[4].
With this definition of thread in mind we might ask, how should threads be classified? One way, and probably the most common way is to classify threads according to how they are scheduled and whether they are preemptive or non-preemptive.
Asynchronous vs. Synchronous
There are two types of scheduling models: asynchronous and synchronous. Asynchronous scheduling means that threads are scheduled independently of other thread. To be independently scheduled means a third component, a scheduler is responsible for dispatching (running) the threads. By contrast, some threading implementations, notably coroutines, are synchronous in that execution is transferred from thread A to thread B by thread A. In these libraries, the transfer is accomplished by a transfer operation usually implemented by the C-Language’s setjmp/longjmp commands. This mechanism is not unlike the familiar function call. In Blizzard’s Lua coroutines control is quasi asynchronous because processor is transferred in two steps: thread A yields the processor by calling coroutine.yield(). No other thread runs until a corresponding coroutine.resume(co) is called..
Asynchronously Scheduled, Preemptive Threads
Preemptive threads are, by construction, asynchronous since they have no control over when they will be dispatched. Contrast preemptive threads with non-preemptive threads discussed next. In the latter case, a thread has to yield the processor thereby signaling the scheduler to pick the next thread to be dispatched,
Preemptive threads are notoriously difficult to use correctly. The difficulties involved in preemptive thread programming are legion. First and foremost is debugging. Most thread programmers are bald having pulled-out their hair trying to debug their code (e.g., timing bugs can lead a programmer to madness). Second, callbacks are highly problematic if not impossible. Third, programmers must serialize access to critical sections (e.g., shared data) and this means locking. The requirement for locking is not only cumbersome but rife with problems. Locking can lead to race conditions, deadlocks, and other horrors. On a single-threaded process like the WoW Client[5], the overhead and complexity of preemptive thread scheduling offer no advantage whatsoever.
But, and this is really important, threads of this class can be made to run simultaneously. Parallelism is the sine qua non of high-end, compute intensive application software. Ironically, this class of threads is what many of us think of when discussions about multithreading arise yet it is the least applicable to general software.
Asynchronously scheduled, non-preemptive threads:
In this model threads are scheduled independently, but not by the underlying operating system. Like the preemptive class above, this class of threads are dispatched by an independent scheduler. The scheduler keeps track of what threads are active, suspended, or completed and resumes those threads when they become eligible for execution.
In practice, both preemptive and non-preemptive threads achieve asynchronicity by executing while-loops in which the thread yields the processor when no work is available.
An analogy might be in order at this point. Suppose a chef needs to fulfill an order for eggs and toast. S/he could fulfill this order in one of three models:
- Synchronous, Single Threaded Execution:
The chef makes the eggs. When the eggs are done, the chef makes the toast. When the toast is done the chef serves the customer cold eggs along with hot toast. This model is very common among unemployed chefs.
- Asynchronous Non-preemptive Multithreading (e.g., Talon):
Immediately after the chef starts the eggs cooking, s/he turns to other kitchen tasks like washing dishes. When the eggs are almost done, s/he starts the toast so that it completes when the eggs are done. S/he then serves the customer hot toast and hot eggs. Most of us work this way naturally.
- Asynchronous preemptive Multithreading (e.g., POSIX):
The chef hires two cooks; an egg cook and a toast cook. The chef then coordinates the two cooks such that the eggs and toast finish at the same time. As in the Talon model, the customer gets hot eggs and toast.
However, the chef has to synchronize the two cooks (for some reason, they don’t communicate with each other and often block each other when, for example, they both need to use the sink). The chef is required to ensure the two cooks will be able to share resources without unduly interfering with each other. In addition, the chef also has to provide a salary for the two cooks. Now that three employees are involved in preparing the eggs and toast, the cost of the meal is correspondingly higher and the customer is charged accordingly. The preemptive model makes sense only when favored by the economies of scale..
Conclusion: asynchronous, preemptive multithreading (e.g., POSIX threads) is employee-oriented while asynchronous, non-preemptive multithreading (e.g., Talon) is task-oriented. Both are useful and have their place. Preemptive multithreading is unbeatable for large-scale, high-end, compute intensive applications that can be structured to run its threads simultaneously.
By contrast, facilities like Talon are particularly effective for task-oriented computing. As it happens, gaming is event-driven and task-oriented. You ought not be surprised to learn that almost all android games, and some modern game engines[6] employ asynchronous, non-preemptive threads as their design center.
Concurrency
By concurrency I am referring to the simultaneous execution of two or more threads. Keep in mind that the simultaneous execution of two or more threads, has nothing whatsoever to do with what constitutes “multithreading”. Talon offers multithreading, but Talon threads can only execute one at a time[7].
Creating WoW Multithreaded Addons
This document describes the WoW Multithreading Library referred to in this guide by its code-name, Talon. Talon threads allow programmers to integrate asynchronous non-preemptive multithreading into their addons[8].
A Talon thread is an abstraction of a lower-level thread called a coroutine. The coroutine is used to execute the Talon thread’s function. Control of a Talon thread is shared between the programmer and the thread scheduler through a data structure called a thread handle. The thread handle is opaque to client code and produced by the thread create service shown here,
local thread_h, result = wow:threadCreate(t, func, … )
In this example, thread_h is the thread’s handle and func is the thread function (executed by the underlying coroutine). The thread handle uniquely identifies a thread and its attributes. The ‘t’ parameter specifies the amount of time a thread will remain suspended after calling threadYield().
The threadYield() function, like the thread create function is a key part of asynchronous multithreading. Specifically, this service causes the calling thread to yield the processor until (1) it receives certain signals or (2) its time interval expires.
These two calls, create and yield, illustrate how the Talon dispatcher works. At thread creation, a time interval in seconds can be specified
Local interval = 0.3 — about 18 ticks of the timer’s clock
local thread_h, result = wow:threadCreate( interval, threadFunc )
Because its interval set to 0.3 seconds, when called threadYield() will suspended the thread for approximately 0.3 seconds. Internally, The dispatcher counts down from 18 and when zero is reached, the countdown is replenished and the thread is [re]queued for execution. Thread creation, by contrast, is treated differently. New threads are created with the thread timer set to zero (0). The causes the dispatcher to queue the new thread for execution as soon as possible[9].
The WoW Multithread Library API
The WoW ThreadLib Library, Talon, is an asynchronous, multithreaded library designed using (and extending) WoW’s Lua coroutine library[10]. The principle extensions are:
- A separate scheduler that handles all resumptions. In other words, while Talon’s API permits programmers to explicitly suspend thread execution (threadYield(), threadDelay(), or threadJoin() they may not explicitly resume a thread.
- A set of basic locking services using mutex objects from which more functional locking can be developed (monitors, semaphores, condition variables, etc.,)[11].
- A simple inter-thread communication system using signals that can be sent to multiple anonymous threads or to a single, targeted thread.
Execution Model
How might addons be structured to take advantage of Talon threads? First, note that the WoW client is not a Talon thread. At the risk of being too obvious, I’ll just point out that one cannot call threadDelay() and expect the WoW client to pause. More generally, when contemplating developing (or modifying) an addon using Talon, many thread services are only valid when executing in a thread context. For example, the getThreadSelf() returns the thread handle of the currently executing Talon thread. Were this function called directly from the WoW client. The getThreadSelf() service only works if it’s called from within another thread’s function. On the other hand, sending signals and creating threads are perfectly fine when called from the WoW client.
Perhaps the greatest structural difference between a normal WoW addon and one modified to take advantage of Talon threads is the use of a top level “main” function, not unlike the main() entry point in a C/C++ program from within which most Talon threads will execute.
For example, the snippet below is a very simple example of the producer-consumer pattern. Here, data_h is the producer thread and main_h is the consumer thread. The thread service, threadJoin() retrieves the return value from the data_h function, getData() and passes it to main_h internally.
local function main(…)
local result = {SUCCESS, nil, nil}
data_h, result = wow:threadCreate( interval, getData )
local returnValue = wow:threadJoin( data_h )
< … additional code … >
end
local main_h, result = wow:threadCreate( 1.0, main )
Note that the main() function is instantiated as a function executes as a thread (see last line of the example). In this snippet, the main thread’s purpose is to retrieve data produced or obtained by the data_h thread. To do this main_h thread “joins” with data_h. When joined, main_h blocks and waits for data_h to complete. Upon completion main_h returns with the returnValue from data_h.
These semantics are not possible except between Talon threads. For example, a producer thread cannot pass its return value to the WoW client and vice versa.
Startup
Talon is initialized and started when the “ADDON_LOADED” event fires and the wow:mgmt_initTimer() service is called.
local function OnEvent( self, event, … )
local addonName = …
if event == “ADDON_LOADED” and addonName == “WoWThreads” then
< … lots of other non-thread initializations …>
wow:mgmt_initTimer( timerInterval )
end
The Thread
The thread is the actual function executed by the procedure referenced in the create thread call. Here is an example of a thread function definition (colored red):
local function threadFunc(a, b, c )
<do stuff>
end
local handle, result = wow:createThread( threadFunc, … )
In common threading parlance, the threadFunc method is also (and often) referred to as a start or action routine.
The Thread Handle
The thread handle is a table of attributes and state variables that are used to uniquely identify a thread and manage its semantics. Its structure is very simple:
Member Element | Description |
TH_EXECUTABLE | The executable image |
TH_THREAD_ADDRESS | A string-representation of the hex-address of the thread executing the thread’s function. |
TH_IDENTIFIER | The unique Id of the thread handle |
TH_STATUS | “running”, “suspended”, “normal”, “dead” |
TH_MUTEX_HANDLE | The mutex handle that the thread currently has locked. |
TH_QUEUED_FOR_MUTEX | True if thread is blocked in a mutex’s wait queue |
TH_FUNC_ARGS | Parameters, if any, to the thread’s function. |
TH_JOIN_RESULTS | The data returned by threadExit() for the calling thread is registered. |
TH_DURATION_TICKS | The duration in clock ticks. |
TH_REMAINING_TICKS | A counter used to determine when the thread is to run. |
TH_DELAY_TICKS_REMAINING | A counter used when managing a delayed thread. |
TH_SIGNAL | Pending signals |
Thread Creation
When a thread is created its state variables (used by the dispatcher to control its execution) are stored in the thread handle. This is illustrated in this function call.
local thread_h, result = threadCreate( t, threadFunc )
Upon successful completion the handle is returned to the caller.
Signaling – Thread-to-Thread Communication
In Talon, signals are integer values used to communicate between threads[12]. See below for a description of these signals. In a future release, Talon will support a more robust signaling interface allowing programmer-defined signaling semantics included signal-associated callback handlers.
Locking and Talon Mutexes
Locking for Blizzard Addons is likely never to be required because Blizzard’s client architecture does not permit preemptive threading. Further, the Lua language does not provide support for the underlying atomic operations necessary for serialization at the instruction set level[13].
Nevertheless, Talon supports a simple mutex (mutual exclusion) object interface. If a thread attempts to acquire a mutex that is possessed by another thread, the former is blocked in the mutex’s wait queue until the latter releases the mutex.
However, Talon’s Mutex API is deprecated and will likely be removed before final release. These services are provided for no other reason than my personal notion that threading without locking is like steak without cattle. Still, I’ve provided a reasonably complete set of primitives because … reasons.
The mutex API’s services are:
mutex_h, result = wow:mutexCreate()
success, result = wow:mutexDelete( mutex_h )
success, result = wow:mutexLock( mutex_h )
success, result = wow:mutexTryLock( mutex_h )
success, result = wow:mutexUnlock( mutex_h )
success, result = wow:isThreadBlocked( thread_h )
thread_h, result = wow:mutexGetOwner( mutex_h )
These services have been tested thoroughly and are working in this particular prototype release.
The Thread Control Block (aka Dispatch Table)
The Thread Control Block (TCB) is a table of thread handles. The thread handles are inserted into the TCB when threads are created. When the thread’s function is dispatched, it will either run until it completes or until suspended via a call to threadYield(). Upon yielding the thread enters the suspended state.
The scheduler runs during every time interval (default is 0.02 seconds). During this interval the dispatcher examines the state of each thread in its TCB and…
- For each thread in the “suspended” state, the dispatcher decrements the thread’s interval tick count. When the interval tick count reaches 0, the original tick count is restored and the thread is resumed. The start routine of the newly resumed thread will continue execution at the point where it yielded.
- For each thread in the “running” or “normal” state no action is taken.
- For each thread in the “dead” (completed) state the dispatcher notifies the reaper thread that moves the expired thread to the graveyard (a table of thread handles whose coroutines are in the “dead” state. At some point later in time, a “burial crew” moves in and sets to nil the handle’s start routine and the handle.
- After N threads have been deallocated, the dispatcher runs the Lua garbage collector using collectgarbage(“collect”). The value of N is settable through the command line.
Signals
This initial release supports three signals. Upon receipt of any of these signals the target thread is queued for resumption on the next tick of the timer – in effect right away. The semantics of each signal is described below
SIG_RETURN | Upon receipt of this signal the thread is to return from the thread’s function thereby terminating the thread. |
SIG_DIE | When SIG_DIE is received cleanup as much state is possible and then call wow:threadDestroy(). After receiving this signal the thread should not attempt to acquire a mutex or otherwise cause the thread to block. |
SIG_WAKEUP | Do nothing. One of your threads wants you to wake up right now and get back to work[14]. |
Signals may be sent via the wow:threadSendSignal() service and received by the wow:threadGetSignal() services.
API
Error Handling
Most, but not all, public functions (those described below) return two values – a boolean success/failed value and a result table containing an error code, error message, and a stack trace. For example, the wow:threadCreate() service returns two values as illustrated below:
local thread_h, result = wow:threadCreate( t,, func, “HANDLE”
thread_h is the thread handle (discussed above) that contains state and semantic variables and a reference to the thread’s function and its arguments if any.
result is a table containing three elements, an integer success/fail code, an error message, and a stack trace.
local thread_h, result = wow:threadCreate( t, func )
if result[1] ~= SUCCESS then
mf:postResult( result )
return
end
The mf:postResult() displays the error message and stack trace in a message window.
Thread Services
Threads are largely understood as overkill when used as simple wrappers around normal functions. Threads are better suited for functions that must execute only when some condition or other arises – for example, the WoW client is heavily event based. Having threads that sleep between events lessens the load on the system.
Create A Thread
Creating threads constitute the heart of any library supporting the creation of multithreaded applications. Below is an example of a call to the function that creates a new thread to run the function, threadFunc(…)
local thread_h, result = wow:createThread( t, threadFunc, … )
This function creates a thread handle, thread_h. The thread handle contains attributes and state information about the thread’s function, threadFunc. The WoW threads handle is functionally similar to the pthread_attr_t object in POSIX. One of the [many] differences between WoW threads and POSIX threads are that POSIX requires the thread handle to be created separately, and prior to the create thread call. WoW threads, unlike POSIX, creates and initializes the handle object as part of thread creation.
If thread creation is successful, the thread handle and a result table are returned and the thread’s function started. If the thread creation fails, no new thread is created, the returned handle is nil, and an error code, message, and stack trace are returned in the result table.
Prototype
thread_h, status = createThread( t, f, … )
Parameters
t | The time (in seconds) a thread will remain suspended after a call to threadYield() |
f | The function to be executed. The POSIX standard refers to ‘f’ as the thread’s start_routine. |
… | A variable parameter list. This list must be a single value or, if multiple values are required, they must be placed in a table. |
Returns
handle | An opaque, unique thread identifier. |
status | A 3 element table containing a SUCCESS or FAILED code, an error message, and a stack trace. |
Delay a Thread
Causes the calling thread to suspend its execution for the specified number of seconds.
Prototype
wow:threadDelay( seconds )
Parameters
Seconds | The number of seconds the caller is to delay before resuming execution. Minimum is currently 0.02 seconds. |
Returns
None
Usage Example
Yield to another thread.
Causes the calling thread to yield its execution to the next thread scheduled for execution in TCB. The calling thread will be subsequently [re]executed when its interval time expires (set by the time interval parameter in the thread creation service).
Prototype
wow:threadYield()
Parameters
None
Returns
None
Usage Example
local function func ( thread_h )
local result = nil
local done = false
while not done do
<addon stuff>
wow:threadYield()
local signal = wow:threadGetSignal()
if signal == SIG_RETURN then
done = true
end
end
end
Get A Thread’s Unique Identifier
Returns the calling thread’s unique Id.
Prototype
thread_id, result = wow:getThreadId()
Parameters
None
Returns
thread_id | integer value |
result | A 3 element table, {SUCCESS/FAILURE, error message, Stack Trace} |
Get a Return Value From another thread’s Function
Allows the calling thread (a consumer thread) to block and wait for data from another thread (a producer thread).
Consider a producer thread whose function generates data to be used by other, consumer threads. Instead of returning the data in a global variable, the producer thread passes its result to wow:threadExit() instead of the normal return. For example, below is an example of threadExit() used in lieu of return.
local function generateData()
data = getData()
wow:threadExit( data ) – replaces “return data”
end
And, here’s the creation of the generator thread:
local generator_h, result = wow:threadCreate( t, generateData )
Knowing the handle of the generator thread, other threads can “join” with the generator thread and retrieve the data generated by its thread function, generateData().
local data, result = wow:threadJoin( generator_h )
The join function blocks the calling thread until the generator_h thread completes and returns its data to any (all) threads blocked and waiting for it to complete..
Prototypes
local returnValue, result = wow:threadJoin( producer_h )
wow:threadExit( returnValue )
Parameters
producer_h | The handle of the thread from which the caller wishes to retrieve data (i.e., join). |
Returns
returnValue | The value to be made available to all threads that have joined with this thread. |
Destroy a thread
This function causes a thread to commit apoptosis (suicide) and in so doing cleanup all held state.
Prototype
wow:threadDestroy()
Parameters
None
Returns
None
Signal Services
Talon currently supports four signals:
SIG_NONE | The default signal. Do nothing |
SIG_RETURN | The signaled thread is to return immediately, i.e. exit the thread’s function thus terminating the thread. |
SIG_DIE | The signaled thread is to call wow:threadDie() |
SIG_WAKEUP | This signal is provided for more efficient event handling. If your thread is an event handler (most of them?) you can create it with a loooooong yield interval. Then, when an event fires, signal the thread with SIG_WAKEUP. My combat logger thread has a yield time of two (2) hours. Between encounters the thread never runs. During an encounter, however, the thread is very, very busy because the event dispatcher is sending SIG_WAKEUP signals at machine gun rates. |
Send a Signal To Another Thread
This service sends a signal to the specified thread
Prototype
local successful, result = wow:threadSendSignal( thread_h, signal )
Parameters
thread_h | Thread to receive the signal |
signal | The signal to send to the targeted thread |
Returns
successful | boolean. True if signal was sent. False otherwise. |
result | See Error Handling above |
Retrieve a Signal
Retrieves a signal sent by another thread. If no signal is pending, SIG_NONE is returned.
Prototype
local signal, result = wow:threadGetSignal()
Parameters
None
Returns
Signal | One of SIG_NONE, SIG_RETURN, SIG_DIE, or SIG_WAKEUP |
Result | See Error Handling |
Usage Example
Appendix
Examples
Thread Creation – Passing Parameters
The signature of the thread creation function is
thread_h, result = wow:threadCreate(t, startRoutine, … )
This first set of examples illustrate the two ways of passing parameters to a thread’s start routine.
Passing a single parameter
This is very straightforward. Suppose we have a function, helloWorld(), that takes a single parameter, greeting;
local function helloWorld( greeting )
print( greeting )
end
To create a thread that will execute helloWorld, we would write,
local greeting = “Hello World!”
local t = 0.25 — seconds
local thread_h, result = wow:threadCreate(t, helloWorld, greeting)
Passing Multiple Parameters
Use a table. Passing lists of parameters such as sum(a, b, c) isn’t supported for reasons beyond the scope of this document. However, you can assemble a function’s parameters into a table and pass the table as a single argument.
local v = {1, 2, 3}
local thread_h = wow:threadCreate(t, sum, v )
The unpack() service can be used to retrieve the parameters from the table.
local function threadFunc( v )
local a, b, c = unpack(v)
…
end
- Formally known as ASR (Automated System Recovery) ↑
- These were user-mode, preemptive threads. ↑
- Peterson, Michael T., DCE: A Guide To Developing Portable Applications, McGraw-Hill, 1995 ↑
- Independent threads are so by virtue of their structure: a thread possesses its own stack, instruction register/pointer, symbol table, and so forth. These structures, especially the stack and its instruction pointer enables a scheduler, for example, an operating system and supporting hardware, to schedule and dispatch threads independent of one another. Interestingly, under this understanding a process (like the WoW client process) is a single thread, in every sense of the word. Coroutines are also threads, but much less resource-intensive than a process like the WoW client. As an aside, when one queries the type of the object returned from coroutine.create(), the returned type is “thread”. ↑
- The WoW client’s implementation of DX12 is multithreaded. ↑
- Unity uses coroutines and is also the design center of approximately ½ of all mobile games in the market today. ↑
- Roblox has a development project in which portions of their coroutine library can run simultaneously. ↑
- Because the WoW client is event driven it is, by construction, asynchronous. This markedly lessens the need for Talon except perhaps for thos3 writing new addons. ↑
- Why not immediately? Because there will likely be other threads queued for execution. ↑
- See https://wow.gamepedia.com/Lua_functions#Coroutine_Functions ↑
- Truth be told, I can conceive of no reason for locking when using asynchronous, non-preemptive threads.. Nevertheless, I added locking because … reasons! ↑
- Signals in WoW ThreadLib are emphatically different from Unix signals. In Unix, signals are hard-coded integers sent to other threads or processes that, when received, cause preordained actions to be taken. When a Unix process receives a SIG_INT signal, for example, the application terminates. In WoW ThreadLib no fixed, preprogrammed signal values (like SIG_INT, SIG_HUP, etc.,) exist. ↑
- I’m referring, here, to atomic instructions such as intel’s LOCK CMPXCHG (Lock Compare and Change) instruction. ↑
- For example, a thread waiting on an event, could use the SIG_WAKEUP signal to notify a logging thread that an event record is ready for logging. ↑