tag:blogger.com,1999:blog-48191230404209157402024-03-12T17:59:37.384-07:00Timothy Lochner's BlogTimothy Lochnerhttp://www.blogger.com/profile/02543950238712857762noreply@blogger.comBlogger11125tag:blogger.com,1999:blog-4819123040420915740.post-60809609930368003282018-08-18T11:24:00.001-07:002018-08-18T11:24:26.960-07:00Mana Engine: Achieving thread safetyI've recently been talking more about the engine I've spent a few years developing. It's been through a lot of revisions, entirely dumping the core of how it works a time or two.<br />
<br />
It's most recent iteration has been developed by myself and Rob Brink (@rob_brink on twitter). We both have a bit of experience developing game engines at this point and wanted to really push a core-neutral, thread-safe, easy to use engine. As Rob has often said (in some variation or another) "It needs to be easier to use than falling on your face."<br />
<br />
There is a great talk in the GDC vault by the tech director of Overwatch, where he goes over the ECS of their engine. At some point he mentioned that some systems can run in parallel, because you can statically know that they won't ever touch the same component data.<br />
<br />
We wanted to take this to the next level by letting the engine handle all of that for you.<br />
<br />
What we came up with (and Rob did most of the implementation of) was a way to generate a graph of dependent tasks. We would statically enforce access to components by type, so that we could guarantee that the engine knew when a component type was being read from or written to. Using this knowledge, it could then build the graph.<br />
<br />
A system ends up looking something like this:<br />
<br />
<pre class="cpp" name="code">class MySystem : public AuthorizedSystem< const MyComponent1, const MyComponent2, MyComponent3 >
{
void Update()
{
//operate on the data.
}
}
</pre><br />
There's a bit more to it than that. For instance, accessing components requires some special syntax so that the engine can know whether this system is allowed to use the data or not. But ignoring those details for a moment, lets see how this helps us write thread-safe code.<br />
<br />
AuthorizedSystem is a special type of system that is templated in a similar way to a tuple. Using some typetraits similar to std::is_const and some other static helpers, we can identify which ones are allows to be written to or read from. The scheduler knows that when two systems don't access the same data, or only read from that data, they can be run in parallel. It also knows that any writers need to go before any readers. There ends up being some possibilities of circular dependencies, and I'll discuss more about resolving those later.<br />
<br />
Currently, in the test game we're making to prove out the engine, this is what our dependency graph looks like.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhndLg-njYvRngCgzcSehl-i1z1JxSs39o34cO94qXYrHlf_8D10tgXoOB_vp7_WveDetn5u4cMgL6rwdO7JCdlZe2QPufQi1On-843Se2akioypF6KeoExgb_RdBIoES-nXhElnQglPmBM/s1600/graph.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhndLg-njYvRngCgzcSehl-i1z1JxSs39o34cO94qXYrHlf_8D10tgXoOB_vp7_WveDetn5u4cMgL6rwdO7JCdlZe2QPufQi1On-843Se2akioypF6KeoExgb_RdBIoES-nXhElnQglPmBM/s400/graph.png" width="400" height="101" data-original-width="1600" data-original-height="403" /></a></div><br />
Side bar: If you're not familiar with webgraphviz.com, it's a very worthwhile and easy to use tool.<br />
<br />
In reality, this is a pruned version of our dependency graph. We have some other systems that I didn't include in this run because I'm not testing them. Rob wrote a nice <a href="https://en.wikipedia.org/wiki/Boids">Boids</a> system that I didn't want to use in this run because I was testing other things.<br />
<br />
And here's a delicious bonus to all this. Want to know what it took to remove the 5 Boids systems? In my main initialization function, I just had to comment out this line:<br />
<br />
<pre class="cpp" name="code">Boid::RegisterSystems(world);
</pre><br />
We also have some auto generated systems that helps do some bookkeeping around components that I've omitted from the graph for the sake of simplicity.<br />
<br />
<br />
Okay, so this looks neat and all, but why does it matter?<br />
<br />
One thing I've noticed over the years as a developer is that some programmers tend to have an affinity for the harder problems, often within a specific domain. We have graphics programmers, engine programmers, gameplay programmers, networking programmers, audio programmers. The list of specializations goes on. Often enough, especially now when multi-threaded programming has yet not been a must-have understanding to write game code, programmers of all sorts of specialties have not needed to know how to deal with multi-threaded problems. <br />
<br />
And in the Mana Engine, they don't have to. The engine is built to help you deal with multi-threaded problems and whenever possible, statically assert that you'll never run into race conditions or performance problems because of things like false sharing.<br />
<br />
Recently, Monster Hunter World on PC was revealed (unconfirmed by the developer) to spend a <a href="https://www.reddit.com/r/pcgaming/comments/9837yl/25_of_cpu_usage_in_monster_hunter_world_is_for/">quarter of it's processing time just managing thread overhead</a>, and had around 100 or so of them. Lock-free and wait-free programming isn't easy. And even if you've solved it before that doesn't mean you won't mess up if you need to do it again.<br />
<br />
Riot's recent <a href="https://engineering.riotgames.com/news/profiling-real-world-performance-league">engineering blogpost about performance</a> shows a graph where the main thread is waiting on the particle thread before it can continue. It'd be nice if, in cases like this, instead of waiting the main thread could assist on some of the work the particle thread is doing. But then you get into other issues where now particle work is being done on the main thread and locking might get screwed up. Rob and I have both seen our fair share of bugs and deadlocks caused by the pattern of letting threads "assist" with another thread's work.<br />
<br />
In the Mana Engine, we don't even have a main thread. We've chosen to solve the problem before it even happens. <br />
<br />
In the Mana Engine, there are always the same or fewer threads than the computer's logical cores and the overhead is absolutely minimal. Solved that problem before it even happened.<br />
<br />
<br />
In future posts, I hope to cover some more details about the Mana Engine, some of the problems we've ran into, and how we've solved them. <br />
<br />
In the mean time, if you have any interest in the Mana Engine and it's details, message me on twitter and I'll be happy to share what we've learned.Timothy Lochnerhttp://www.blogger.com/profile/02543950238712857762noreply@blogger.com0tag:blogger.com,1999:blog-4819123040420915740.post-29090862164295200692016-12-28T13:27:00.000-08:002016-12-28T13:56:11.886-08:00The Data You Don't Care AboutIf you haven’t read <a href="http://tlochner.blogspot.com/2016/08/the-data-you-care-about.html">this post</a>, you probably should before continuing. It kinda sets up everything.<br />
<br />
In the last post, it was all about the Data You Care About and making sure that your methods are only bothering with such data. This post is about the other data – Data You Don’t Care About. It begs the question – if you don’t care about it, why am I even writing about it?<br />
<br />
Data You Don’t Care About is all about bookkeeping. It facilitates doing things to the data you do care about. As in the previous post, the <code>m_Count</code> and <code>m_Space</code> data members of the example array class is this type of data. I really want to push a point here – it’s data that help you <strong>do</strong> things to the data you care about. As a result, this data is intrinsically tied to functions.<br />
<br />
Enter, the Doer classes.<br />
<br />
Okay, okay. So there’s a bad stigma around Doer classes. They have long names, they often wrap only a single function, and can actually make your program harder to reason about. Yes, I agree, if done poorly, Doer Classes are all those terrible things. In fact, the very video that started me on these blog posts explicitly says they’re ugly and bad. In the context he’s talking about they are bad – I’ve seen that kind of code first-hand. What I’m proposing is a way to go about it that really… isn’t bad. Because really what we’re doing is associating bookkeeping data with the functions that do the bookkeeping. Classes just provide us a way of controlling access to that bookkeeping.<br />
<br />
In the last post, I gave some examples of game systems that might want to respond to changes in data. These systems are doers. They can very often start as just a single function, but when you need to introduce bookkeeping (often for the sake of optimization, but sometimes other reasons) keeping the bookkeeping data with your function is quite useful.<br />
<br />
Let me just throw out an example to make it a bit easier to grasp. Let’s say you have a system for rendering 3D objects in your game. You could loop through your Model components and let each one be a draw call, but there’s a better way to do it. You could organize them by mesh, so that you render all of one type of mesh all at once – probably using hardware instancing. Doing this organization will cost us a bit on the CPU, but will save us far more on our draw call count and the GPU.<br />
<br />
<pre class="brush: cpp">// The below code depends on the understanding, so here's a quick low-down of some of the objects and methods in the example:
// Declared elsewhere:
// Model: A Component that has data related to a model (ie, mesh, material, etc).
// ModelId Model::GetModelId(): returns the ModelId.
// Transform: A Component that has data related to position, orientation, and scale.
// const Matrix44& Transform::GetWorldMatrix(): returns a Matrix44 that represents the transform's world matrix.
// ComponentId: A shared ID for all components that belong to the same entity. Ie, the key that binds them together.
// ModelId: An asset ID for the model.
// Set: A Hash set.
// void Set::Insert(key): Inserts a key into the set.
// void Set::Remove(key): Removes a key from the set.
// size_t Set::Count(): returns the number of keys in the set.
// Map<T_Key, T_Value>: A container of Key-Value-Pairs.
// T_Value& Map::FindOrCreate(T_Key): finds the value mapped to this key. If it doesn't exist, it creates it.
// T_Value* Map::Find(T_Key): Finds the value mapped to this key. Returns nullptr if it doesn't exist.
// Array<T>: A generic dynamic array class.
// void Array::Reserve(size_t count): reserves count spaces in the array. Good for making sure growth happens only once.
// Will not shrink the array is count is already less than the Array's space.
// void Array::Clear(): removes all elements in the array -- does not shrink the array's allocated space.
// void Array::Push(const T&) pushes a copy of T onto the array.
// Matrix44: A 4x4 matrix.
class RenderingSystem
{
typedef Set<ComponentId> ModelInstanceSet
Map<ModelId, ModelInstanceSet> m_ModelInstances
void OnModelCreated( Model& model )
{
//Create a new entry in the ModelInstanceSet if there is an associated transform.
ComponentId compId = GetComponentId(model);
ModelInstanceSet& instanceSet = m_ModelInstances.FindOrCreate(model.GetModelId());
instanceSet.Insert(compId);
}
void OnModelDestryed( Model& model )
{
ComponentId compId = GetComponentId(model);
ModelInstanceSet* pOldInstanceSet = m_ModelInstances.Find( oldId );
if(pOldInstanceSet)
{
oldInstanceSet->Remove(compId);
}
}
void OnModelIdChanged( Model& model, ModelId oldId )
{
ComponentId compId = GetComponentId(model);
ModelInstanceSet* pOldInstanceSet = m_ModelInstances.Find( oldId );
if(pOldInstanceSet)
{
oldInstanceSet->Remove(compId);
}
ModelInstanceSet& newInstanceSet = m_ModelInstances.FindOrCreate( model.GetModelId() );
newInstanceSet.Insert(compId);
}
public:
//All systems get a few virtual functions like this. Update, FixedUpdate, etc.
void Draw() override final
{
Array<Matrix44> transforms;
for(ModelInstanceSet& instanceSet, m_ModelInstances)
{
transforms.Reserve(instanceSet.Count());
for(ComponentId compId, instanceSet)
{
Transform* pTransform = GetComponent<Transform>(compId);
if(pTransform)
{
transforms.Push(pTransform->GetWorldMatrix());
}
}
// And then here to do the rendering part where you bind the model buffer, the instance buffer (the transforms)
// and make the drawcall using your favorite Graphics API's instance drawing method, such as D3D's DrawInstanced()
// method or OpenGL's glDrawArraysInstanced() function.
}
}
}
</pre><br />
Of course the real advantage to this set up isn’t that a single system can do this. It’s that this system can operate and never had to know about any other system. Dozens of other systems could manipulate the transform data and the <code>RenderingSystem</code> would never know and would never need to know. That’s the beauty. You can add a <code>PlayerControllerSystem</code>, <code>PhysicsSystem</code>, <code>AIControllerSystem</code>, or whatever else to push and pull the objects around and the <code>RenderingSystem</code> doesn’t care.<br />
<br />
Moreover, the <code>RenderingSystem</code> can make optimizations that won’t interfere with the other systems. For instance, rebuilding the instance buffer every frame is a bit excessive, and we could change <code>ModelInstanceSet</code> from a typedef to a struct containing the Set and a dirty flag, and the instance buffer. If it’s not dirty, we don’t rebuild it. The dirty flag would need to check for some additional things, like when transforms are created or destroyed, if there is a <code>Model</code> with a matching ComponentId, but that’s all done here, inside this one file.<br />
<br />
The last few things I’m going to bring up about how much I like this take on Object Oriented Programming, are the following:<br />
<br />
<ol><li>If for any reason this particular rendering system needed to be gutted and replaced with something else. Maybe you’re changing graphics APIs, or the guy who originally put it together was an absolute goof and wrote it horribly, you can safely extract and replace it with whatever you need.</li>
<li>If for any reason you don’t want the rendering system at all (ie, on a server, or a command-line client) then you just don’t instantiate it. The client can still instantiate one, and then all the client and server have to do is keep their component data in sync.</li>
</ol><br />
So back to the data you don’t care about – that’s exactly what the <code>ModelInstanceSet</code> is all about. You care about it for bookkeeping that can make it the game perform faster or smarter, but it’s not the actual data (the actual data you care about are the components). It provides modularity that it can be dropped in or taken out easily.<br />
<br />
This all gets to the point from the first blog post and the video that spurred me to write it. Object Oriented Programming, as it is currently utilized in all too much of the professional world, really is bad. But I don’t think that it means <em>all</em> OOP is bad, and I hope these two posts provide sufficient example of how OOP can be used well.Timothy Lochnerhttp://www.blogger.com/profile/02543950238712857762noreply@blogger.com0tag:blogger.com,1999:blog-4819123040420915740.post-89916099567388866972016-08-28T21:34:00.001-07:002016-12-29T07:41:51.956-08:00The Data You Care AboutI recently watched a video about why <a href="https://youtu.be/QM1iUe6IofM?t=33m30s">Object Oriented Programming is Bad</a> and later <a href="https://twitter.com/tloch14/status/770079142838763521">tweeted a bit about it</a> and quickly realize twitter wasn't the right platform to adequately describing my thoughts about a particular part of the video.<br />
<br />
So here's my thoughts better laid out. Keep in mind, this is only in reference to what he says at 33:30 into the video -- not the entire video.<br />
<br />
Basically the point he's making (and that I want to expand on) is that methods and the idea of encapsulation that they support are not always bad. He says that when the method is tightly related to the data of the class, it's appropriate. A common example are ADTs -- Arrays, Lists, HashLists and other generic containers and constructs.<br />
<br />
The question I want to elaborate on is "Why do methods work out okay for ADTs but not other classes?" and "How can we use that to inform how we write other stuff?"<br />
<br />
Here's the answer up front: Bookkeeping.<br />
<br />
If you think about data (literally, the variables you declare) always keep in mind which ones are Data You Care About and which ones are data that do bookkeeping for the Data You Care About. Let's open up an array to see what I mean.<br />
<br />
<pre class="brush:cpp">template<typename T>
class Array
{
T* m_pArray; // The actual array of data.
int m_Count; // How many elements are in the array.
int m_Space; // How much space has been allocated for the array.
public:
// Constructors, accessors, etc.
}
</pre><br />
If it's not obviously, only one of the members of this sample Array class are the actual data we care about -- the other two are just bookkeeping. The key thing here is that within the class, the various methods are manipulating m_Space and m_Count in order to keep track of how much memory has been allocated and how much memory has been initialized. If these were publicly exposed, anybody could write to these methods and screw up the classes accounting of data. In reality, if you know the internal structure of the array class, you could do some casting and pointer arithmetic to manipulate these values anyway. But that's not a big deal because something like that obviously looks like a very unsafe thing to do and you have to go out of your way to do so. Whereas array.m_Count = 10; <i>looks</i> like a perfectly normal line of code and you'd have to evaluate the surrounding code before realizing it was bad.<br />
<br />
Like I mentioned in my tweets about this, this is more common than just ADTs, and in fact when you start separating data in your head into "real data" and "bookkeeping" you'll design your classes with much more clarity. Take a typical 3D transform class.<br />
<br />
<pre class="brush:cpp">class Transform
{
Vector3 m_Position; // Local Position
Quaternion m_Orientation; // Local Orientation
Vector3 m_Scale; // Local Scale
mutable Matrix4x4 m_WorldMatrix;
mutable bool m_WorldMatrixDirty = false;
public:
// Constructors, accessors, etc.
}
</pre><br />
Let's assume that m_Position is initialized as a (0,0,0), m_Orientation is an unrotated quaternion, and m_Scale is (1,1,1). The world matrix is initialized as an identity matrix. Maybe Vector3 is padded to 4 floats instead of 3 or any number of better decisions than how this class is laid out. The point is, there is Data You Care About and bookkeeping (AKA overhead).<br />
<br />
This example is a bit deceiving because in the end, we probably only care about m_WorldMatrix. The local Position Orientation and Scale (POS) is likely just used so that when this transform's parent changed, we still have our data relative to the parent and can easily reconstruct our world matrix, which is used for rendering, physics, and many other systems. Note that this class isn't currently describing who the parent is -- could be bound by pointer or ID or something. It doesn't matter for the example being shown.<br />
<br />
The obvious bookkeeper is m_WorldMatrixDirty. It's especially notable because it's got the mutable keyword. That means I can modify it within a const method. It makes this possible:<br />
<br />
<pre class="brush:cpp">const Matrix4x4& Transform::GetWorldMatrix() const
{
if(m_WorldMatrixDirty)
{
//recalculate world matrix.
m_WorldMatrixDirty = false;
}
return m_WorldMatrix;
}
</pre><br />
Now, we are only calculating the world matrix when it actually needs to be calculated. But to the outside world, they have no idea we're doing this trick. However, just as importantly, we don't want people directly writing to our local POS, because when the local POS changes it invalidates our world matrix. So we write something like this:<br />
<br />
<pre class="brush:cpp">void Transform::SetLocalPosition(const Vector3& newPosition)
{
m_Position = newPosition;
m_WorldMatrixDirty = true;
}
</pre><br />
This is almost like <a href="https://en.wikipedia.org/wiki/Event-driven_programming">Event-Driven Programming</a><sup>1</sup>. We'd guarded the access to our data member because we want to make sure we do something when that value changes. You can even imagine delegates and events used to notify other parts of the code when data has changed and they want to react to those changes. Here's some easy examples:<br />
<br />
<ul><li>In a game, when something is added or removed from your inventory:<br />
<ul><li>The UI wants to know so it can update your inventory window.</li>
<li>The chat/info box wants to know so they can show a message (ie, "Removed Steel Sword").</li>
<li>If it is a networked or online game, a system that replicates data may want to notify your client that the item was added or removed.</li>
</ul></li>
<li>In a game, when your health changes:<br />
<ul><li>Enemy AI may want to prioritize their targets. If you have low enough HP, maybe they just want to finish you off.</li>
<li>Friendly AI may want to prioritize their healing or protective abilities.</li>
<li>The UI will want to show the health change.</li>
<li>The game may want to make your character grunt from the hit if it was large enough.</li>
<li>In a networked game, a replication system needs to notify nearby clients of the change.</li>
</ul></li>
<li>In a level editor, when you make a change to the heightfield:<br />
<ul><li>The terrain mesh will need to be rebaked for rendering, collision, pathfinding, etc.</li>
<li>Placed objects may want to move with the terrain as it is being deformed.</li>
<li>A terrain texturing system may want to change the texture based on height, slope, or any other number of properties of the new mesh.</li>
<li>Flora may want to regenerate -- maybe the grass only grows on flat terrain and not hills. It wants to know if you just made a steep hill.</li>
</ul></li>
</ul><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://i.imgur.com/TdQCv2n.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://i.imgur.com/TdQCv2n.gif" width="320" height="142" /></a><br />
<p>Or the UI reacts to a change in stats.</p></div><br />
I could go on and on with examples.<br />
<br />
The big takeaway is that none of this automatic bookkeeping could be done without methods (or at least some form of indicating which functions were allowed access to data members). Methods certainly <i>are</i> useful and maybe even more commonly useful than the author of the video is letting on. I'm not saying he's wrong -- just that it's slightly more nuanced than the video describes. And maybe that's just a result of only having so much time in a video to explain things. Only he'd really be able to comment on that.<br />
<br />
Next post I'll talk about who might want to be listening to these events and what type of data those objects are likely to have (hint, it's not Data You Care About, and that's okay).<br />
<br />
<br />
<br />
<br />
<sup>1</sup> Full disclaimer -- event driven programming has it's faults too -- it certainly shouldn't be used everywhere.<br />
Timothy Lochnerhttp://www.blogger.com/profile/02543950238712857762noreply@blogger.com0tag:blogger.com,1999:blog-4819123040420915740.post-77728786494103299252016-01-29T12:35:00.002-08:002016-03-26T16:55:28.046-07:00"Me Too" MMOsI was reading through an excerpt of a speech from Jeff Strain. I've read the speech many times before throughout my career -- it was given in 2007 -- but reading it nearly a decade later I found myself unsure of the words being said.<br />
<br />
Here's the excerpt:<br />
<br />
<i>"Before you start building the ultimate MMO, you should accept that “MMO” is a technology, not a game design. It still feels like many MMOs are trying to build on the fundamental designs established by UO and EQ in the late ’90s. In the heyday of Doom and Quake we all eventually realized that “3D” was a technology, distinct from the “FPS,” which was a game design. It’s time we accepted that for MMOs as well. We are finding ways to overcome many of the limitations of the technology that dictated the early MMO design, such as Internet latency and limited global scalability. These improvements can enable a new class of online games that break out of the traditional MMO mold and explore new territory. It can be a daunting proposition to willfully walk away from what seems to be a “sure thing” in game design, but lack of differentiation is probably the number one reason that MMOs fail, so we all need to leave the comfort zone and start innovating, or risk creating yet another “me too” MMO."</i><br />
<br />
The speech is somewhat prophetic at the end, saying "me too" mmos are likely to fail.<br />
<br />
What's interesting is that 9 years later we've seen a lot of new types of MMOs. Survival MMOs, the MMORTS, social oriented online games. Even strictly non-combat MMOs like Ever Jane. The list goes on. But at the same time, we've still seen a lot of "Me Too" MMOS. Many have failed, but a fair number of them have succeeded. Perhaps most strangely is the fact that Guild Wars 2, the successor to Guild Wars and made by the company co-founded by Jeff Strain is very much a "Me Too" MMO that is succeeding. When Guild Wars 2 was first presented to the Guild Wars community, it was even pitched as "an MMO more like what you think a typical MMO is like" (paraphrasing here). The most notable difference is the persistent explorable zones. And while the dynamic events are an attempt at innovation, it's more of an evolution (Quests -> Group Quest -> Warhammer Online's Public Quests -> Guild Wars 2's Dynamic Events). Hearts are especially quest-like in their lack of real impact on the world your character lives in.<br />
<br />
Now, by my second-hand hearing, Guild Wars 2 has been much more successful than Guild Wars 1 was from a monetary standpoint. I don't know if that's because of the more traditional design (granted, with plenty of non-traditional mechanics thrown in the mix) or a result of something else. Either way, I'm reading these words differently today than I was the last time I set eyes on them.Timothy Lochnerhttp://www.blogger.com/profile/02543950238712857762noreply@blogger.com0tag:blogger.com,1999:blog-4819123040420915740.post-90749506840116359452016-01-16T15:39:00.000-08:002016-01-16T15:45:50.870-08:00Blending PixelsA task at work has recently required me to do some graphics stuff. I'll be the first to admit that as a learned discipline I'm not a graphics programmer. I enjoy almost any kind of programming and in my hobby work I find graphics programming especially fun. But simple things in that realm of the programming world I am embarrassingly ignorant of. Even something like... blending two pixels.
<br />
<br />
But! That wasn't going to stop me from researching and validating the right way to alpha blend two pixels together.
<br />
<br />
Now in reality, the task isn't so much for pixels, but rather for heightfield data. Now often heightfield data is just a single-channel pixel anyway, but in our engine heights are represented as floats. To expand the feature set of our terrain engine, our heightfield data needed to be modified to include layers and an alpha channel. So each heightfield sample is now two channels -- height and alpha.
<br />
<br />
Of course, googling and grabbing an algorithm isn't all there was to it. I wasn't really going to be satisfied with the answers I found unless I could test it myself. Sure, I could plug the code in and see what results I get, but it was much faster to throw up my favorite web app -- Desmos Graphing Calculator.
<br />
<br />
<iframe height="400px" src="https://www.desmos.com/calculator/e1tbnm6nj8" width="100%"></iframe>
<br />
<br />
Compared to my last documented use of Desmos, this is absurdly straightforward. I'm visualizing the alpha channel along the x axis and the height channel along the y axis. I'll admit, It's a bit disorienting until you get used to it. Regardless, <i>a/h<sub>3</sub></i> is the resulting value. <i>a/h<sub>1</sub></i> and <i>a/h<sub>2</sub></i> are the inputs. Alpha values are normalized, so we're working with ranges between 0 and 1.
<br />
<br />
The formula for the output ends up looking like this in code. As usual this code is for illustrating the formula -- not for being performant code.
<br />
<br />
<pre class="cpp" name="code">
struct HeightSample
{
float height, alpha;
}
void Blend(HeightSample& out, const HeightSample& a, const HeightSample& b)
{
out.height = a.alpha * a.height + b.alpha * (1.0f - a.alpha) * b.height;
out.alpha = a.alpha + ((1.0f - a.alpha) * b.alpha);
}
</pre>
It's interesting to note that while the alpha blending is a commutative operation (that is, if you swap the input values, the output is the same), the same it not true of the height channel. This is because sample <code>const HeightSample& a</code> is considered the "top" pixel. They are not even commutative if both alpha values are 0.5. This isn't particularly intuitive, so I thought it was worth mentioning.Timothy Lochnerhttp://www.blogger.com/profile/02543950238712857762noreply@blogger.com0tag:blogger.com,1999:blog-4819123040420915740.post-3488929130363480732015-12-22T12:57:00.000-08:002016-01-12T01:10:47.225-08:00Waste of Length in Vector LibrariesI've recently been on a bit of a vector math hook lately which naturally comes with coding up things, testing, toying, and experimenting. Not just vectors, but basically everything commonly used in game development -- boxes, spheres, intersections of shapes, rays, matrices, etc.<br>
<br>
I wanted to better understand Sphere-Line collision test, so I made this up a few nights ago.<br>
<a name='more'></a>
<br>
<iframe height="400px" src="https://www.desmos.com/calculator/o8suf6fmit" width="100%"></iframe><br>
<center>
<a href="https://www.desmos.com/calculator/o8suf6fmit"> Click here to go to graph.</a> </center>
<br>
There are a few extra unused variables in there. For instance, variable <i>a</i><sub><i>1</i></sub> is the angle between <i>x<sub>3</sub></i> and <i>x<sub>4</sub></i> converted to degrees. I don't use it in actually calculating the intersection points, but it was a useful reminder to me that the process of finding the angle between two vectors is almost entirely part of finding the intersection between a sphere and a line. It's literally just missing a call to <code>arccos()</code>.<br>
<br>
It got me to thinking about how many other vector operations do this sort of thing where two operations will calculate the same thing independent of each other. How many times in our programs do we redundantly calculate something twice?<br>
<br>
One thing that immediately stuck out to me was the length of a vector. Programmers should hate <code>Length()</code> ( or Magnitude or whatever you want to call it). Length requires both a call to <code>sqrt()</code> and a division, both of which are (generally) <a href="http://stackoverflow.com/a/7726267/804338">several times more costly</a> than sum and multiply. We even avoid the <code>sqrt()</code> call if we're only comparing two lengths. Ignoring the sqrt call doesn't change the outcome of the comparison, so why do the extra work?<br>
<br>
So then I thought, if I already know the length of a vector, why can't I pass that length in to a function that would calculate the length anyway?<br>
<br>
Consider the following:<br>
<br>
<pre class="cpp" name="code">// Lets say this function causes the class it belongs to to look
// at a vector if the vector is close enough. Kinda contrived, but
// it illustrates the point.
void LookAtNearbyTarget( const Vector3& vec )
{
const float length = Length(vec);
if( length < m_FarSight)
{
return;
}
// later on in the function...
const float angle = Angle(vec, Vector3(0.0f, 1.0f, 0.0f) );
// use the angle for something like creating a rotation matrix
// by which to turn a game object.
}
</pre>
<br>
Aside from grabbing the length directly, <code>Angle()</code> will also calculate the length of <code>vec</code> and use it. Here's a decent implementation of <code>Angle()</code>. I've changed it to create some temporaries for readability and I'm not worried about inlining or anything. I'm just trying to illustrate. <br>
<br>
<pre class="cpp" name="code">float Angle(const Vector3& vec1, const Vector3& vec2)
{
const float length1 = Length(vec1);
const float length2 = Length(vec2);
const float dot = Dot(vec1, vec2);
return acos( dot / length1 / length2 );
}
</pre>
<br>
Ah! Now, not only are we calculating a length the program already knew, we're also calculating a length the program could have statically knew. After all, we just passed it a statically typed normal vector facing up. Lets see if we can't make this better. And I'll inline the dot value this time. <br>
<br>
<pre class="cpp" name="code">float Angle(const Vector3& vec1, const float length1, const Vector3& vec2, const float length2)
{
return acos( Dot(vec1, vec2) / length1 / length2 );
}
</pre>
<br>
Now let's revisit our LookAtNearbyTarget function. <br>
<br>
<pre class="cpp" name="code">void LookAtNearbyTarget( const Vector3& vec )
{
const float length = Length(vec);
if( length < m_FarSight)
{
return;
}
// later on in the function...
const float angle = Angle(vec, length, Vector3(0.0f, 1.0f, 0.0f), 1.0f );
}
</pre>
<br>
Great. Now we're saving some cycles. Of course, there are times when we don't know the two lengths, and we don't want to lose the convenience of a function that will calculate the lengths for us, so lets implement this too. <br>
<br>
<pre class="cpp" name="code">float Angle(const Vector3& vec1, const Vector3& vec2)
{
return Angle(vec1, Length(vec1), vec2, Length(vec2) );
}
</pre>
<br>
And if you really wanted to go nuts, you could even add these. Part of me thinks "eh, this is getting silly." but another part of me says "Why not? If nobody uses it, no big deal. If someone does find it useful, then good for them." <br>
<br>
<pre class="cpp" name="code">float Angle(const Vector3& vec1, const float length1, const Vector3& vec2)
{
return Angle(vec1, length1, vec2, Length(vec2) );
}
float Angle(const Vector3& vec1, const Vector3& vec2, const float length2)
{
return Angle(vec1, Length(vec1), vec2, length2 );
}
</pre>
<br>
Personally, I think stuff like this is <i>really</i> important. Why? Because believe it or not, not all programmers are good at vector math. In fact, most programmers I know have admitted they don't like vector math. It is entirely unlikely that someone who doesn't like vector math will write performant vector math code. Heck, I like vector math, but my work project doesn't provide a standard <code>Angle()</code> function, so whenever I needed the angle between two vectors, I would naively something write this: <br>
<br>
<pre class="cpp" name="code">const float angle = acos( Dot(Normalized(vec1), Normalized(vec2)) );
</pre>
<br>
Why? Because everything I'd read about finding the angle between two vectors has said "to find the angle between two normalized vectors, get the arccosine of the dot product". Without having the time to investigate how I could optimize this (this is game development people, we have deadlines), I took that at face value and went with it. And sure, this gets the job done. And when I'm not in a tight loop or operating over 100's of objects, this is probably okay and not really a performance impact. Still, when I think back about some systems I've written, I've certainly put this in code where I'm operating over lots of vectors.<br>
<br>
But it's not just about performance either. Not every programmer is good at vector math. So when a programmer who has to dabble in vector math for a task says "I need the angle between these two vectors... crap, how do I do that again?" he's now wasting valuable development time googling "Angle Between Two Vectors", reinterpreting someone else's psuedo-code into the particular library he's working on, and then steping through it slowly to verify that it's actually doing what he thinks it is. And if he messed up and gets an unexpected result, he's sitting there wondering whether the answer he got wasn't right. Maybe he should look for a different answer. Debugging and testing your own code becomes laborious.<br>
<br>
Instead, someone who is good at vector math can take the time (a trivial amount for them of course) to implement a few functions that are performant, well tested, and easy to use for other programmers. When provided with the tools to do so, even the most junior of junior programmers can write performant code or at the very least, when time for code review comes, it's very easy to spot places where performance can be improved.<br>
<br>
Some Extra Credits<br>
For those interested, using the naive implementation of <code>acos( Dot(Normalized(vec1), Normalized(vec2)) );</code> also has a few more downsides.<br>
<br>
First, <code>Normalized()</code> will be creating a temporary. Two for that matter in this case.<br>
<br>
Secondly, <code>Normalized()</code> will divide each element by the length. When instead you divide by the length of each vector after you already have the dot product, you are only dividing twice (once for each vector) instead of six times (once for each element of each vector.Timothy Lochnerhttp://www.blogger.com/profile/02543950238712857762noreply@blogger.com3tag:blogger.com,1999:blog-4819123040420915740.post-38970295634859172232015-04-07T09:51:00.001-07:002015-04-07T12:16:12.018-07:00Thinking Out LoudI tweeted this out last night:<br />
<br />
<blockquote class="twitter-tweet" lang="en"><p>What would my <a href="https://twitter.com/vanguardsoh">@vanguardsoh</a> peeps say about the diplomacy sphere being turned into a mobile game? Obvs have no green light. Just wondering.</p>— Timothy Lochner (@tloch14) <a href="https://twitter.com/tloch14/status/585332321777131521">April 7, 2015</a></blockquote><script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script><br />
<br />
I did so because I was sitting in bed, searching for good puzzle games to play on my phone. I enjoy simple puzzle games when I have down-time. It's something that I can do at my own pace and leisure. If there are any time-constraints to the game, they're often in short bursts like "solve the puzzle as fast as you can" which only takes about a minute anyway. <br />
<br />
After not really finding anything in the app store I was particularly interested in, my mind drifted to games that would fill this need I have to play a quality puzzle game that I can do in very short sessions, or chain them together for an engaging longer session.<br />
<br />
I thought of Vanguard's diplomacy sphere. Don't know why, but I did. I also like wondering and thinking about how a game design would play out and discussing it with others, so I tweeted to see what other people thought.<br />
<br />
What I didn't expect was to wake up to this:<br />
<br />
<blockquote class="twitter-tweet" lang="en"><p><a href="https://twitter.com/hashtag/Daybreak?src=hash">#Daybreak</a> dev muses about mobile <a href="https://twitter.com/hashtag/Vanguard?src=hash">#Vanguard</a> diplomacy game: <a href="http://t.co/EVyC5Z1sop">http://t.co/EVyC5Z1sop</a> <a href="https://twitter.com/DaybreakGames">@DaybreakGames</a></p>— MassivelyOverpowered (@MassivelyOP) <a href="https://twitter.com/MassivelyOP/status/585444389536792578">April 7, 2015</a></blockquote><script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script><br />
<br />
My first thought after reading this was that a better question is, "What kind of loser uses the word 'obvs'? Did I really use that?" *facepalm*. <br />
<br />
The next things was that the tweet was probably misleading because I mentioned a "green light". As if it was even remotely possible that it could happen. <br />
<br />
My intent for mentioning "no green light" was to attempt to be clear that this is just thinking out loud. After reading it this morning, it sounds like "I have no green light now, but maybe I could if it was a good enough idea" or something like that.<br />
<br />
Sorry to say, nothing is brewing. It would be a neat challenge to design diplomacy as a stand-alone card game (holy crap, a lot of context would have to be filled to replace the actual game of Vanguard) but it ultimately is just not a thing right now.<br />
<br />
<br />
That being said, if you know of any good mind-bender or puzzle games for the iPhone that aren't a thinly veiled attempt to nickel and dime the user, let me know. Here are two that I have enjoyed so far, plus some more action-y game too.<br />
<br />
<ul><li><a href="https://itunes.apple.com/us/app/hexy-the-hexagon-game/id974045016?mt=8">Hexy - The Hexagon Game</a> by Valcour Games - Although I wish it had more content. The picture puzzle thing seems neat, but I don't really want to solve pictures from my own phone.</li>
<li><a href="https://itunes.apple.com/us/app/flow-free/id526641427?mt=8">Flow Free</a> by Big Duck Games LLC - A decent amount of content you can play for free. More purchasable if you want.</li>
<li><a href="https://itunes.apple.com/us/app/solomons-keep/id365183754?mt=8">Solomon's Keep</a> and <a href="https://itunes.apple.com/us/app/solomons-boneyard/id387497198?mt=8">Solomon's Boneyard</a> by <a href="http://www.raptisoft.com/">Raptisoft Games</a> - Very entertaining games to play. Really dug into Solomon's Keep. Only just started Solomon's Boneyard. And the in-app purchases aren't very in-your-face. The games are quite enjoyable without them.</li>
</ul>Timothy Lochnerhttp://www.blogger.com/profile/02543950238712857762noreply@blogger.com3tag:blogger.com,1999:blog-4819123040420915740.post-35328507973932803212014-09-03T21:29:00.001-07:002014-09-03T21:29:19.779-07:00Order of Construction in C++ for Polymorphic ClassesI came across a bit of a bug (or at least I thought it was) recently while programming. I thought I'd share the experience because it's a bit of a nuance of the C++ language and doesn't seem to fit what we normally expect C++ to do.<br />
<br />
The issue was with polymorphism. I was creating a new class that derived from a base class. Part of the construction of the base class is to give it a pointer to it's owner. Upon construction, the base class would then call into the owner class, passing the <code>this</code> keyword to it. What I expected was to have all the vtable information of the derived class available to me. This is a key element to polymorphism that makes it such an incredible feature. However, when Owner began using the Derived object I had created (as a Base*), I found that it was calling only Base functions, not overloaded Derived functions.<br />
<br />
Surely, I thought, I've discovered a bug in the compiler or something.<br />
<br />
And then I decided to dig a bit deeper and try it out in a very controlled environment. This is the test I came up with.<br />
<br />
<pre class="cpp" name="code">#include <iostream>
using namespace std;
class Base
{
public:
Base()
{
bool isDerived = GetIsDerived();
if (isDerived)
{
cout << "Class is derived.";
}
else
{
cout << "Class is base.";
}
}
virtual bool GetIsDerived() const { return false; }
};
class Derived : public Base
{
public:
Derived() : Base()
{
bool isDerived = GetIsDerived();
if (isDerived)
{
cout << "Class is derived.";
}
else
{
cout << "Class is base.";
}
}
virtual bool GetIsDerived() const { return true; }
};
int main(int argc, char** argV)
{
Base base;
Derived derived;
}
</pre><br />
I have a Base class and a Derived class that extends Base. I then have a virtual function in Base called GetIsDerived(). This function always returns false, because in Base, that's true. In the Derived class, however, I overload it to always return true. In my entry point function, I create a new instance of Base on the stack. Sure enough, as you would expect, the first line printed is "Class is base." When I create the new Derived object on that stack, you'd think that the overloaded GetIsDerived() would run, but when <i>within the Base constructor itself</i> no vtables have been created yet, and as such, no overloaded functions will get callled. The total output is then this: <br />
<br />
<pre class="plain">Class is base.
Class is base.
Class is derived.
</pre><br />
So you see, when putting functionality in the constructor, rather than mere initialization, be careful what your functions are doing because until the object is <i>fully</i> constructed whatever is using it will treat it like the base and not like the derived class. The problem is even more difficult to see when you're giving someone else your <code>this</code> keyword and they begin using your pointer as the base, not the derived.Timothy Lochnerhttp://www.blogger.com/profile/02543950238712857762noreply@blogger.com0tag:blogger.com,1999:blog-4819123040420915740.post-55792458349275773952014-08-27T23:10:00.002-07:002016-03-26T17:05:06.886-07:00Programming as a communication to future programmers (including yourself)I was recently in a discussion with one of my colleagues about some of the finer points of developing in C++. To be frank, we talk about this nearly every day, but this conversation in particular stuck out to me.<br />
<br />
The particular thing we were talking about was how the code you write can clue in future developers into how something ought to be used or how something works. In C++, this is pretty important because C++ allows developers to accomplish tasks in a variety of ways. The particular example we were discussing was when to pass by reference and when to pass by pointer.<br />
<br />
Surely, there have been thousands of discussions on whether to pass by reference or pointer. Just google it and you'll see the evidence of that. However, instead, we took the approach of "When should I <i>accept</i> by reference or by pointer?" Moreover, what does this say about how I want to use the references or pointers you are giving me?<br />
<br />
I phrase the question this way because the user of a function doesn't get to choose how the function's signature is created -- the used function chooses that. By creating your function in a particular way, you are communicating how it will be used by user code. As an example, when thinking about passing by reference or by pointer, the two have rules in the language that permit certain things and restrict others. Therefore, depending on how you accept an object, you're communicating to the user how you expect to be able to use it.<br />
<br />
If you are accepting by pointer, you are saying that <br />
<ol><li>It is okay for the object to not exist, ie, it's optional. Pointers can be null, so only accept a pointer if you have a handle-case for the object to not exist. In fact, it's probably best to put all pointer arguments at the end of the function signature with default " = nullptr" values in each one, making the function more convenient to use.</li>
<li>The object, if not null, will at least live as long as the function's scope. Once you leave the function's scope, the object could at any time be deleted. This means that the function shouldn't store off the pointer somewhere else for later use. Of course, this much isn't even guaranteed because...</li>
<li>The function can delete the pointed-to object. This is often my biggest fear when using other people's code and passing objects to them by pointer. Does the function expect to take over ownership? Will the function delete my object? Of course, some functions explicitely are intended to do that and even say so in their name. </li>
</ol><br />
As opposed to accepting a reference where...<br />
<ol><li>The object is guaranteed to exist.<sup>1</sup> This is great because then there's no null-checking, the user knows that the object must exist, and if they attempt to dereference a pointer in order to pass it by reference, the dereferencing null exception happens on their turf, not mine -- it's their mistake, not a bug in my API. However, like a pointer, there is no guarantee that the object will exist longer than the scope of the function, so don't store it off or even the object's address off anywhere.</li>
<li>The object will live for the entire scope of the function and you are guaranteed to have access to it for the entire duration of the function. Whereas with a pointer, I could re-assign it to point to a different object, with a reference I can't reassign it. Moreover, this means that...</li>
<li>A reference cannot be deleted (with exceptions). Try calling delete on a reference. It doesn't work. Of course, you could call "delete &reference;", but let's be honest, if you're doing that, you have no business being a developer at all.</li>
</ol><br />
And there's more about communicating to your user how you will use objects passed to them. Using the const keyword is a great way to promise to your user code that you won't modify the object passed to them (unless you cast it to a non-cost, at which point somebody needs to cut you... deep... and in a main artery).<br />
<br />
Part of me feels dumb writing this. Above, I said that this was a finer point of developing in C++, but honestly it's a pretty blunt topic. This isn't an obscure practice. C++ developers have known these points since the dawn of the language. Heck, compilers even auto-generate copy constructors that take a const reference. Why? Because they never want to copy null and they want to guarantee that the copy constructor won't modify the original object.<br />
<br />
Yet, I see time after time after time, code written that doesn't follow these simple rules, and it's often by developers who've been at it for well over a decade. Instead of guaranteeing the existence of an object by asking for it by reference, they'll ask for it by pointer and put an assert at the very top of the function. That will prevent shit from hitting the fan in debug, but when the shit hits the fan in release, you're shit outa luck and will likely have a harder time finding the bug because release dumps never give you enough information (optimizations that make the callstack not match your code, only getting stack memory, etc).<br />
<br />
In my mind, it all comes down to subtle communication and usability. I'm a tools programmer, so I think a lot about usability. What many programmers don't think about is that the first tool is the code itself. The usability of the code also needs to be considered in detail and that includes what it communicates to users of the code. <br />
<br />
A comparison might explain it best. If you have a property of an object in a tool you're writing and that property can be one of a list of values that do not relate to each other in scale, the UI representation you'd likely create for that is a combobox. Why? Because there are three discrete options and you can only pick one. But what if you create a slider? Well a slider can snap to 0, 33, 66, and 100% to represent the four values and only one can be selected. But a slider communicates a relationship between the possible values. A slider is, therefore, confusing.<br />
<br />
Good uses of sliders: Graphic quality (scaling from low to high), view distance (scaling from near to far), field of view (scaling from narrow to wide).<br />
<br />
Good uses of comboboxes: Physics Interpolation (none, interpolate, extrapolate), a character's profession (warrior, mage, ranger, priest), light type (point, spot, direction, area).<br />
<br />
If you were to use a slider for the fields that lend themselves to comboboxes, the user could get confused. Wait, is a priest the best profession because it's highest on the slider? Is a point light not as good as a directional light because it's lower on the slider? Of course not, but when you present a slider to something that fits a combobox, the user will get confused and be unsure of how to use the tool.<br />
<br />
This is what it's like when seeing a function call that is asking for an object in the wrong way. Possibly worst of all, it slows down future development time as confused programmers stumble their way through learning your API. <br />
<br />
Write so as to communicate. When you write, think, "what am I telling future developers about my code?" and use what is most appropriate.<br />
<br />
<br />
<i><sup>1</sup> While a reference is not absolutely guaranteed to exist, it is at least guaranteed to never be assigned to null statically. Because we're talking about programmer's static typing here, for all intents and purposes, a reference says "I am expecting this object to exist".</i>Timothy Lochnerhttp://www.blogger.com/profile/02543950238712857762noreply@blogger.com0tag:blogger.com,1999:blog-4819123040420915740.post-81095855324752863062014-04-23T19:50:00.001-07:002014-04-25T01:34:59.107-07:00Code Dump: C++ delegateWhen I started to learn to program, a lot of stuff seemed like magic. If you're a programmer, you probably know the feeling. It's just like when you are told how to do something, rather than getting an explanation of how it works under the hood.<br />
<br />
For me, that happened all the time using C# delegates. See, I really liked C# delegates. I thought they were the shit (and what all the pros used, don't ya know). Turns out they aren't good in every situation (most situations call for a more simple and sufficient event pattern) but regardless, they can be very useful at certain times.<br />
<br />
<a name='more'></a><br />
<br />
When building UI elements, they can be incredibly useful, especially if they are made to be robust and can adapt to a lot of situations. Now, as a predominately C++ programmer, I've found myself needing to use delegates on more than one occasion. The only problem is that C++ doesn't natively support delegates. I'm cool with that -- its a low level language, and as Bjarne Stroustrup is known to say, C++ is general, so that libraries can be build to suppose specific needs. Not all disciplines need to use delegates, so it doesn't exactly need to be a feature of the language (lots of people will disagree with that, and that's okay).<br />
<br />
In any case, if you <i>want</i> to use a delegate pattern in C++, here's a code dump that can help you get started. The code is marked up with comments to add a bit of clarity of what's going on. Be warned, you'll need to know a good deal about templates to understand what's going on.<br />
<br />
<i>Note: I copy and pasted this from a project of mine, and simplified it a bit for the sake of this blog post. The concept works, although I haven't actually built this particular snippet of code, so you may find warnings or errors in it.</i><br />
<br />
<pre class="cpp" name="code">// This is just a templated interface. The actual delegate will have a pointer to this type, that the actual callback objects will be stored in.
template<typename TReturn, typename TParam> class ICallback
{
virtual TReturn Execute(TParam& param) = 0;
};
// Inheriting from ICallback. This object is a callback for a particular function (defined by the return type and parameter) on a particular class (TClass).
template<typename TClass, typename TReturn, typename TParam> class Callback : public ICallback<TReturn, TParam>
{
typedef TReturn(TClass::*Function)(TParam& param);
TClass& m_rObject;
Function m_pFunction;
public:
Callback(TClass& rObject, Function pFunction)
: m_rObject(rObject)
, m_pFunction(pFunction)
inline TReturn Execute(TParam& param)
{
return (m_rObject.*m_pFunction)(param);
}
};
template<typename TReturn, typename TParam> class Delegate
{
ICallback<TReturn, TParam>* m_pCallback; //Pointer to interface, so we can story any Callback inheriting from ICallback<TReturn, TParam>.
Delegate() : m_pCallback(NULL);
~Delegate()
{
ClearCallback();
}
template<typename TClass> SetCallback(TClass& rObject, TReturn(TClass::*pFunction)(TParam& param) )
{
ClearCallback();
m_pCallback = new Callback<TClass, TReturn, TParam>(rObejct, pFunction);
}
void ClearCallback()
{
if(m_pCallback != NULL)
{
delete m_pCallback;
}
}
TReturn Execute(TParam& param)
{
if( m_pCallback != NULL)
{
return m_pCallback->Execute(param);
}
return (TReturn)0;
}
};
</pre>
<br />
Here is the general usage<br />
<br />
<pre class="cpp" name="code">class MyClass
{
//Create some struct to pass relevant information to the callback function
struct EventObject { int m_EventData };
//Declare the delegate
//Note: To handle the event outside of MyClass (maybe someone is listening to MyClass's
Delegate<void, EventObject> m_OnEventDataChanged;
public:
//On construction, set the callback.
MyClass()
{
m_OnEventDataChanged.SetCallback(*this, &MyClass::OnEvent);
};
//Declare the callback event.
void OnEvent(EventObject& eventObject)
{
//Do something
}
//During some function, some event happens
void SetEventData(int newEventData)
{
m_EventData = newEventData;
EventObject eventObject;
eventObject.m_EventData = m_EventData;
m_OnEventDataChanged.Execute(eventObject);
}
};
</pre>
This is a fairly primitive implementation of a delegate class. I intentionally left it this way so that others reading this might take it and make it their own. Here's a list of some things you may want to do to improve it's functionality.<br />
<br />
<ul>
<li>Overload the Delegate's () operator to make it a functor (or <a href="http://en.wikipedia.org/wiki/Function_object">Function Object, according to Wikipedia</a>). Just have the () operator take the TParam as the parameter and essentially wrap Execute(). This will make it feel more like a C# delegate.</li>
<li>Use a smart pointer to manage the Callback object.<br />
</li>
<li>Use a container to contain multiple callbacks. SetCallback() becomes AddCallback(). Then, multiple objects can listen to a single event. And if you're going to go down this route, you could:</li>
<ul>
<li>Add a RemoveCallback().</li>
<li>Overload += and -= to add or remove callbacks (this makes it feel a bit more like a C# delegate, but can make things messy since we have to pass two parameters for setting the callback (the function, and the object on which the function will be called). I'd generally say this one isn't worth it, unless you're doing some heavy rewriting.</li>
</ul>
<li>Add methods for suppressing the event. This way, the client could call m_OnEventDataChanged.Suppress() and m_OnEventDataChanged.Unsuppress() to suppress the event. This can be handy, if you make SetEventData() have a second bool parameter that when true (and by default is false), won't fire the event.</li>
<li>If you go with making it possible to suppress the event, you could also make a SuppressGuard class. The class would construct taking in a reference or pointer to the delegate to be suppressed, and automatically call Suppress() on it. The destructor of the SuppressGuard would then call Unsuppress() The usage for suppressing goes down to a single line ("SuppressGuard suppressGuard(m_OnEventDataChanged, bSuppress);") and you'll never cause a bug by forgetting to suppress it.</li>
</ul>
<br />
That's about it. If you're knowledgeable on the subject of delegates in C++, feel free to chime in and point out flaws or places for improvement. Everybody needs a good code review, even me. =PTimothy Lochnerhttp://www.blogger.com/profile/02543950238712857762noreply@blogger.com0tag:blogger.com,1999:blog-4819123040420915740.post-25221003510475961092014-04-23T19:02:00.001-07:002014-04-23T19:02:46.873-07:00Dumb name is dumbI've done some researching about how to start blogs. One thing that comes up a lot is to make sure you nail the blog name.<br />
<br />
So I sat in front of my the "Create a blog" page for a few minutes, realizing that I feel like I'm naming a character in an MMO (you MMO gamers know what I'm talking about).<br />
<br />
Then I realized that I can change the name at any time. So here's my blog, with a dumb (yet accurate, which the programmer in me is a fan of) name.<br />
<br />
It's not much now, but hopefully that will change. You can expect to see posts about stuff I'm interested in (like the choreography of 1's and 0's) and from time to time, even my own original thoughts (coming from an American, this is pretty impressive), so come back again and follow me and all that stuff. You never know, I may actually entertain <i>some</i> of you.<br />
<br />
Note: While I am a programmer for Sony Online Entertainment, you won't find any information regarding their games here. My thoughts and everything written on this blog are my own.Timothy Lochnerhttp://www.blogger.com/profile/02543950238712857762noreply@blogger.com0