Bugs of the day, Tuesday 2013-03-26

One bug, and one better solution to a previous bug.

Local static state is local, static

int Foo() {
  static int timeout = kTimeoutLimit;
  bool polled_condition = CheckExternalCondition();
  if( !polled_condition )
    --timeout;
  if( timeout == 0 ) { 
    printf("condition not set before timeout");
    return kTaskFailed;
  }
  if( polled_condition )
    return kTaskComplete;
  else
    return kTaskIncomplete;
}
void Bar() { 
  while( Foo() == kTaskIncomplete );
}

Sometimes a local static variable is a useful thing — conveniently quick to add to any piece of code, and localized to a particular scope. You know everything about what can happen to it during its entire lifetime — assuming you know about how the scope it is contained within operates. (and I’m disregarding the returning of references to local static variables from a function. That’s a whole different bag of fun…)

Quickly rewriting some code today, I added a local static counter — counting down each time a desired condition was not met, and triggering a printf() should the count ever reach zero. I was using this to diagnose that something unexpected had happened, and that some event had not taken place.

The above code should work adequately well — if Bar() is only ever run once (and only from one thread at a time — but that’s not the issue here). timeout is never reset, so unless CheckExternalCondition() returns true, first time, every time, the value in timeout will continue to decrease with each run of Bar(). If it drops below zero, there will no longer be a limit on the on how long Foo() can loop.

A fix here is to make sure timeout is reset each time Foo() “completes” i.e. when it returns kTaskComplete or kTaskFailed.

More generally, this does not seem like a good use of a static local variable — relevant context for the function is not readily apparent within the function, so it seems better to store the timeout value outside of Foo().

(The actual code is more convoluted than the example above, so it’s not quite as simple as adding a local timeout var in Bar(), but only just)

Redux: Changed virtual function prototype results in correct function not being called

This was a bug where I changed the prototype for a virtual function, but that classes with the old prototype would not be detected at compile time and would silently do nothing should the virtual function be called. Read the details via the link above.

I had added an assert to catch any classes that didn’t have the new version of the function, but that only worked at runtime, and only when the function was actually called. What I really wanted for this was a compile-time check that was more reliable than the inheriting programmer remembering to use override. I discovered that there is a way to do this with C++11: final.

class Base {
public:
  virtual void Foo(int some_param, WhatsitType some_other_param)  {  }
private:
  virtual void Foo(int) final { }
};

With this, anything that inherits from Base and attempts to define its own void Foo(int) method will result in a compile error. I’m much happier with this, and making this change revealed some extra instances of the old function that I’d missed in my previous sweep. Huzzah.

Bugs of the day, Monday 2013-03-25

Initialized, but not in the right order

One runtime bug today, being fallout from an attempted fix for Friday’s Unexpected use of class bug.

To recap/elaborate: I changed something like this

struct Foo : public Baz {
  Bar* m_Bar;
  virtual void Destroy() override { }
};

to be something more like this

struct Foo : public Baz {
  Bar* m_Bar;
  virtual void Destroy() override { m_Bar->Destroy(); }
};

and I had assumed this would be OK because I [thought that I] knew the only place that Foos would be created, which would always safely initialize m_Bar.

Unfortunately, there is another way to construct these objects with some unexpected (bad) input data that would not initialize m_Bar. In such a case, Foo::Destroy() would crash because m_Bar pointed at who-knows-what.

So I implemented something like

struct Foo : public Baz {
  Bar* m_Bar;
  virtual void Init() override { m_Bar = NULL; }
  virtual void Destroy() override {
    if( m_Bar != NULL)
      m_Bar->Destroy();
    else
      HARD_TO_MISS_NOTIFICATION( USEFUL_WARNING_37 );
  }
};

“Hooray,” I thought. “That will catch those pesky bad datas.”

In practice what I got was a crash from the first thing that tried to use m_Bar.

It turns out that the construction path for Foos sets up m_Bar before calling Foo::Init(), so now our correctly constructed Foo object has it’s valid data overwritten by an overzealous initialization function.

Foo::Init() is called from both the preferred and not-preferred construction paths, so my hope had been to fix it there and catch use of the not-preferred path, but this didn’t work. My next thought was to reorder the setup of m_Bar and the call to Init(), but that seemed far too risky — there are a lot of subclasses of Foo, and many of them have non-trivial Init() overrides of their own, so there is a high probability that something else will break if m_Bar is not set up before the call to Init().

The winner this time seems to be to initialize the member to zero using: a constructor. Yes, it’s probably the obvious option, but for much of this codebase explicit Init() functions are used instead of constructors. Fortunately, for Foo and its friends (sub classes, parent class) we call the constructor (via placement new) to set up the vtable for us, so we can use it to also initialize the m_Bar pointer and consequently help catch occurrences of bad data in a less fatal fashion.

 

Bugs of the day, Friday 2013-03-22

Terrible day — between an unfamiliar system, the unshelving of the remains of half a changelist and a whole lot of carelessness, I made a lot of mistakes and got to do a lot of full recompiles. A day to learn from.

So before I summarize that disaster, I’ll start with a could of other bugs.

Unexpected use of class

A couple of days ago, I checked in some changes to a class whereby one of its member classes could dynamically accrue allocations during its lifespan — it keeps track of certain memory allocations to reduce the number of allocations needed during its lifespan.

I subsequently added a Destroy() method for that member so that it could be cleaned up properly at the end of its life, expecting that the member would be correctly initialized so that Destroy() would Do The Right Thing.

One crash callstack later…

There exists two ways to create objects of the class type. One — used to construct objects in a particular family — will perform the correct initialization. The other — a more general construction path used to construct a wide variety of other more-or-less-related object types — will not correctly initialize this member type.

I was of the understanding that, for various architectural reasons, nothing would attempt to construct an object of this type via the more general approach. It should be no surprise that I was wrong.

I am of the opinion that anyone using the “general” construction path is Doing Something Wrong, and so I think it will be appropriate to add an assert to catch this kind of thing [a little awkwardly] in future. I do need confirmation from from other users of this system that we agree that what has happened here is undesired/unexpected, and that it can be assumed to be Wrong in future.

The “general” construction path is used to dynamically construct object based on class data passed into the program. It is likely possible to prevent this kind of “bad” data from entering the program by more strongly protecting it at the data-construction stage, rather than trying to prevent it at runtime. I’m not sure what protection is in place presently and need to investigate that.

The absolute minimum fix is to prevent this code from crashing should it get the wrong/unexpected data…

One of the issues leading to this problem is that the type being used here derives from a common base class. This does offer a great deal of convenience in practice within this codebase, but it also leads to this kind of complexity (source of bugs) where Bar isMostlyButNotAlwaysA Foo.

Hard-coded array size, no checking

This one arrived as a crash callstack in some code I had a lot to do with a while back, that had remained largely nearly touched for a number of months. The code is heavily optimized and is reasonably complex — and quite awkward to modify. As a result, having to locate and fix bugs in this code is a somewhat daunting task, not least because of how much use it has received (every frame…), and has been running without problems for a long time.

Fortunately (HAHAHAHA), it seems the problem was really quite simple: this code uses some fixed-size static arrays for storage of intermediate data. At no point is there a check that the data being processed does not exceed the size of these arrays. As is the way of these things, the size of the data being processed has grown over time, resulting in a silent writing past the end of one of these arrays. Crashilarity ensues.

Fix: increase the size of the arrays, add an assert to make sure we have enough space.

(Removed the comment on one of the arrays asking why it was so small in the first place :\ )

General disaster

  • Double-init of system — whereas yesterday I was failing to init the system I was working with, today I managed to call the init function twice. Added a flag and check to detect this.
  • Double-shutdown of system — Later on in the day (after I’d got past most of my other bugs), I discovered I was calling the shutdown function for the system twice, in exactly the same way as I’d been double-calling the init function. I should have checked for this when I fixed the double-init bug (where there’s smoke…)
  • Failure to flush — started writing a prefix token to a buffer with an incorrect initialization order. Data didn’t get flushed, record of memory blocks was confused, things failed.
  • Need to reset state after flush — once the buffer is flushed to disk, reset all the variables that tracked the un-flushed state.
  • Flush [too] early and flush [too] often — put calls to various Flush() methods from a range of class destructors. Got a little carried away. Let it mellow.
  • Calling functions on the right objects — sometimes it’s nice to be able to construct things from other things, and to be able to rely on object lifetimes to encode certain behaviors. Even so, it will usually matter which object’s method gets called…
  • Case matters. Sometimes. — When interacting with an external unfamiliar system, it’s worth making sure you know whether it is case sensitive. In this instance, it was, and I wasn’t. I worked this out almost by accident, which is regrettable.

Post-disaster-mortem: working through this code was highly reactive — I spent far too little time reading the code and working out what it was/should be doing, and far too much compiling, running the app, eyeballing output, manually feeding it into the next system, and trying to work out what I’d done wrong each time. I have no doubt that manually tracing through [a relatively small amount of] code, and validating its function up front would have saved me a lot of time.

Note to self: know when you’ve fallen into the reactive compile/test/modify cycle and step back to look at the bigger picture.

Bugs of the day, Thursday 2013-03-21

Failed to initialize instance-specific member variable correctly because I was calling the wrong constructor

struct Foo {
  bool m_B;
  Foo(Bar& bar) : m_B(true) { /*do something else with bar*/ }
};
struct Baz : public Foo {
  Baz(Foo& foo):Foo(foo) {}
};

Thinking that Baz::Baz(Foo&) was calling the defined construct-from-reference-to-bar constructor that would initialize m_B to a particular state when in fact I was calling the compiler-generated copy constructor that would not.

Yet another case of pay attention-to-what-you’re-doing. The thing that got me this time was that this codebase doesn’t have a lot of construct-from-reference, so led to the trap of if it compiles, it’s probably OK.

Quite possibly this was fallout from yesterday’s the slicing-inheritance bug where I changed inheritance to a pointer-member, but didn’t change the construction call.

Use of uninitialized system

Calling functions in a particular system without having called the appropriate System::Init() function. The results aren’t pretty.

This was caused by carelessness when un-shelving a partial rework of the init and use of the particular system.

Keeping track of where particular code is being called requires extra effort when managing multiple versions of the same files. Clearly, I’m too easy to confuse and need to be more careful, and to work with fewer variations.

In general, checks/asserts in the external interfaces to a system are probably worthwhile to catch this kind of thing a little sooner.

Yet another uninitialized member variable crash

In a rework of a rework of a rework of some code I lost a couple of struct member initializers.

Init to zero, all works as expected. Sigh.

End of day reflection:

Making changes to a previously-working system, it would have been profitable to spend some time to capture the known-good output (and a record of the known-extant bugs) at the start of the process. I spent some time reverting all my changes and re-building to confirm my analysis of these things, which was not the most efficient approach.

Bugs of the day, Wednesday 2013-03-20

Uninitialized class member

The problem:

struct Foo {
  int m_A;
  int m_B;
  int m_C;
  Foo():m_A(0), m_B(0) {}
  void Bar() {
    DoSomethingWith(m_C); // fail it
  }
};

My defense is that this was spread across a header and a .cpp file, which is to say I was careless. (My preference really is to put all code inline in one place — .h and .cpp is repetitive and costs time and effort to keep in sync)

This was some existing code that I reworked a couple of weeks ago and hadn’t got to the point of running/testing until today. I’m really not sure how I managed to remove the initializer here — it existed in the original version.

The fix:

Initialize to zero, which was not just known-value, but correct-starting-value.

Prevention:

Always initialize everything. Keep class definitions in sync with constructors and Init() methods.

Sliced inheritance

The problem:

class Bar {
  virtual void Baz() {/*nothing interesting*/}
};

class BarExtender: public Bar {
  virtual void Baz() {DoSomethingInteresting();}
};

class Foo : public Bar {
  Foo(Bar& bar): Bar(bar) {}
  void Yolo() { Baz(); }
};

BarExtender b;
Foo f(b);
f.Yolo(); // intended that this would DoSomethingInteresting(). Actually does nothing interesting

The fix:

class Foo : public Bar {
  Foo(Bar& bar): m_Bar(bar) {}
  void Yolo() { m_Bar.Baz(); }
  Bar& m_Bar;
};

Prevention:

Know the language well enough to know when you’re writing code that doesn’t say what you mean.

Undesired printf output

The problem:

This is the first “functional” bug — something that didn’t work quite the way I’d intended.

For a data serialization class, I had hoped to replace several functions of the form:

WriteInt32(int32_t i) { snprintf(..., "%d", i); }
WriteDouble(double d) { snprintf(..., "%f", d); }

to a single function:

WriteNumber(double d) { snprintf(..., "%f", d);  }

Unfortunately, the %f format specifier will, by default, always write a decimal point and 6 figures thereafter, which is undesirable for integers-types-cast-to-double written by this function — for particular use cases it doubles (hah!) the size of the data written.

The fix:

I’ve not tested this yet, but it looks like %g (or probably %.ng will provide the desired behaviour by not writing the surplus zeroes. I will need to determine if the output for floating point input and large integers will be acceptable.

Prevention:

I still have a lot to learn about printf format specifiers, though I’ve found http://www.cplusplus.com/reference/cstdio/printf/ useful when considering/trying to to understand the available options. This does make me wonder if a printf-format-string constructor exists analogous to http://cdecl.org

Update:

Rather than trying to find a single format that works for all input or testing the number before printing it, I’ve opted to overload the function by type. This way we can use knowledge of the type to do something appropriate and more efficient.

I have overloads for int, unsigned int, double, long and unsigned long, and a comment as to why there’s no implementation for unsigned long long (the loader treats all numbers as doubles, which means we could end up writing out numbers that we can’t read back without loss of precision along the way). I’ve also added static asserts in case this code gets compiled on a system where sizeof(long) > sizeof(int). There are separate functions for writing out bool and pointer values.

Bugs of the day, Tuesday 2013-03-19

Changed virtual function prototype results in correct function not being called

This one wasn’t a bug that landed immediately from my change, but happened when a subsequent code branch was integrated. I’m going to claim this as my bug as it could have been prevented (or at least made more obvious) with some extra though.

The problem:

We have a virtual function, something like

class Base {
public:
  virtual void Foo(int some_param)  {  }
};

Which I modified a few weeks back by adding an extra parameter, so foo() became

  virtual void foo(int some_param, WhatsitType some_other_param) { }

and I added the same parameter to every one of the classes that inherited from class Base. There were quite a few.

Sometime later,  code was integrated from another branch where the old prototype was still in use, including something like this:

class NewInheritor : public Base {
public:
  virtual void Foo(int some_param) { // oh noes - it's the old prototype!
    DoAmazingThings();
  }
};

The new code compiled without warning, but at runtime, NewInheritor::Foo() wasn’t being called, so the amazing things were failing to be done. Debugging this was somewhat inefficient — the process for calling Foo() methods includes a number of steps, and I failed to notice the missing parameter for quite a while.

The fix:

Update NewInheritor::Foo() to match the parent class.

Preventing these in future:

Remember that virtual inheritance is a horribly fragile beast.

One thing that may have worked is to make Foo() pure (compile error) — or undefined (link error) — in the base class (i.e. void Foo() = 0;). This isn’t a great match in this case as inheriting classes don’t need to have their own implementation of Foo() to be valid, and requiring it makes for some additional tedious boilerplate code in every inheritor (of which there are many).

The override keyword will help detect this at compile time, but it relies on the implementer of NewInheritor to have used it :\ The action here is to recommend that people use override on every virtual function override.

In this case, what I do know about this code is that it is never valid to call Foo() on a class that doesn’t have its own implementation. We can put an assert() in Base::Foo() and let the writers of inheriting types (or me when next I get to debug one of these) have a more obvious clue about what’s happening. Adequately functional.

memcpy() into an uninitialized pointer variable

The problem:

I had changed the type of a variable from being a static array to being a pointer, to match a change I’d made to a particular function. What I hadn’t paid attention to was that this array was being used as the destination address for a call to memcpy(). An uninitialized pointer on the stack is not a good place to try to copy things.

The call to memcpy() did not cause the program to crash. The program did crash shortly thereafter, though, which distracted me for a short while from the cause.

The fix:

Explicitly allocate some space for the copy.

Preventing in the future:

The pointer on the stack had not been set. A null pointer would have caused the crash to happen at the call to memcpy(), making the cause more obvious.

Bad loop counting

The problem:

int max = m_Max;
int i = 0;
for( ; i < max; ++max )
  if( a[i] == bar )
    break;

++max? Yeesh.

m_Max is initialized to zero, which means that this was a no-op rather than an infinite loop.

The fix:

Increment the counter, not the upper bound.

Preventing in the future:

Don’t be a goose.

Failing to store updated state

The problem:

Immediately following the code above, in the case where we failed to find the desired bar in the array, increase the size of the array. And then fail to store the new max size into m_Max.

I even got this wrong twice: my first attempt was to replace a use of max+1 with ++max.

The fix:

Write the necessary state back to the object: ++m_Max.

Preventing in the future:

Pay attention to where the state should go. Don’t be a goose.

Stop the bugs

I like to try and fix as many of the bugs that I write as early as possible, before they’re deployed to the rest of the team — I hate inconveniencing others with my own defect-laden code. As such, my preference is to do what I can to catch the bugs I write as early as I can.

I rely very heavily on the compiler to catch particular classes of bugs for me, to the point that (unlike my early computer science lecturers) I don’t look at a compile error to be a bug: the compiler is very good at reminding me about parts of the code I’m writing that I haven’t finished changing yet, and does so quickly and with minimal fuss. If I change the assumptions or guarantees of a particular function, deliberately changing the function so that callers will no longer compile is — for some projects — not a terrible way to start tracing through parts of a codebase that might be affected. This can include changing arguments to a different order, more args, fewer args, changing the function name, #if 0/#endif around the function, etc.

There are various scenarios where this may not be so useful — very widely used functions, virtual functions, changes to a library used by a number of disparate programs, etc — but I find that it can be more efficient than grepping through a large number of files, particularly when there’s some ambiguity in the symbol name you’re searching for.

Some other ways to have the compiler error for you include leaving symbols undefined so that the compiler error list becomes something of a todo list, making class members private, removing methods from a base class. There are probably others I use. Many of these changes can be applied as a short-lived change to learn more about potential consequences of a change, and are typically easily revertable (assuming the use of any basic version control system).

Compiler warnings are also well worth considering and, if you operate with warnings-as-errors, impossible to avoid. I’ve found plenty of bugs that would have be horrible to diagnose from their symptoms thanks to the warnings of a good compiler.

Compile-time/static asserts are also a good thing. You can never have too many of those.

After the compile-time stuff, next is linker errors. If it all compiles but won’t link, the undefined symbol list can again become that todo list of functions that you haven’t yet got around to implementing. The downside with link errors is that for a larger program it takes a lot longer to compile and attempt linking the whole thing before you find out about the work yet to be done. There are other tricks that can be done with the linker to deliberately break the build, but that’s beyond what I want to write about right now.

When it all compiles and links, finding the bugs is a matter for the runtime. You need to run the code and find out if it will work or not (for the purpose of this article, I’m going to treat “running a test suite” as “running the code”). Any defects present at this stage typically take more time to identify, and more time to fix — even if it is something blatantly obvious, the edit, compile, link, run cycle can be much longer than just edit, compile.

It’s at this point I get to my intended purpose of this blog post: I want to write fewer bugs. Various classes of bugs are prevented for me by the compiler and linker, but if it’s something that gets all the way to failing at runtime (whatever that means), I’m left disappointed. What to be done about it?

What I’m going to try today and, hopefully, continue to do after today, is keep track of bugs I wrote that survived to the point of being run. I’ll give some thought to how they got there and consider if there was something I could have done (and can do in the future) to prevent that kind of bug. And maybe I’ll find out if I keep making the same kinds mistakes over and over again, or if I can learn to write fewer bugs.

I shall attempt to begin with today’s bugs in another post.

Some notes on memcpy

Recently I encountered a bug due to memcpy() being used like this:

    memcpy( elem, elem + 1, n * sizeof(Element) );

What’s the problem?

The prototype for memcpy is defined something like:

    void* memcpy(void* dest, const void* source, size_t size);

memcpy is wonderfully simple – use it to copy size bytes from source to dest. But the definition of memcpy from the standard goes something like this:

“The memcpy() function shall copy n bytes from the object pointed to by s2 into the object pointed to by s1. If copying takes place between objects that overlap, the behavior is undefined.” [source]

If you notice the example at the top of the email, the copy is from elem + 1 to elem with a size of (potentially) more than one Element. It’s quite possible that objects overlap. And they did.

The magic word in the definition is undefined. When you do something that is undefined in C or C++, anything can happen. The compiler can format your hard drive, email a resignation letter to your manager, there could be nose goblins. Or your data could become corrupt. Relying on undefined behavior is a bad idea.

Nose goblins notwithstanding, what can actually go wrong for memcpy? If memcpy is implemented as walking forward through the source range and writing to the destination, the above case should be OK, right?

Well, maybe.

But if it starts at the end of the source array and works backwards, the destination range will end up with corrupt data.

Probably.

Even if memcpy walks forward, data could be corrupted if the source has a lower address than the destination (and they overlap). There are plenty of ways to write a fast memcpy implementation that can corrupt data for overlapping ranges. And implementations that will cause problems exist and are widely used: e.g. http://lwn.net/Articles/414467/ Even better are implementations that will step forward through memory for small values of size, and backwards for larger ones.

Conclusion: don’t use memcpy for overlapping ranges. Instead, use memmove which is well defined if the ranges overlap:

“The memmove() function shall copy n bytes from the object pointed to by s2 into the object pointed to by s1. Copying takes place as if the n bytes from the object pointed to by s2 are first copied into a temporary array of n bytes that does not overlap the objects pointed to by s1 and s2, and then the n bytes from the temporary array are copied into the object pointed to by s1.” — [source]

(With thanks to Andreas for his help in finding the source of the bug that inspired this piece)

Processor architectures: seeking source material

To satisfy my own curiosity I want to do some writing about CPU architectures – analysis, comparison, origins, history/evolution, variants, etc. I envision writing blog posts, possibly many — it depends on how things go.

CPUs/families that I’d like to know more about include (off the top of my head) x86, ppc, arm, mips, m68k/coldfire, z80, 6502, SuperH, sparc, alpha, m88k. (to start with — there’s many more with interesting stories :)

Before I write about these, I want to be well informed. As such, I’m looking for recommendations of enlightening things to read. In many cases I can look at technical documentation provided by vendors, but these usually don’t provide a lot of background, context or history of an architecture.

What has been written about these (or other) CPU architectures? I’m keen to know about the origins and evolution of architectures. What forces (technical, financial, marketing, etc) influenced designs? How have they changed over time? I’m interested in comparisons between architectures, implementation details, extensions, variations, anything that will illuminate the reasons, benefits, deficiencies, and more about a design.

So, what should I be reading? (or who should I talk to? :D)

[I’ve asked the same question over on google plus, and I will be collating answers there]

Review of Careers in Gamedev presentations
[aka Memoirs of a Career Motivational Speaker (retired)]

In my previous post, I wrote about my outline and intent for talking about getting into a career in game development (or elsewhere). In the end, I delivered the presentation ten times at five different high schools. Here are my recollections and reflection on the experience.

High School 1

The first high school was a lot of fun. As I mentioned in the previous post, I was somewhat uncertain about what would constitute an appropriate pitch and this was my first such talk to a group of unknown teenagers — which makes it exciting/scary/fun :)

I talked to two groups of around 40 students back to back. There was a mix of grades (7-10) and a pretty good balance of male/female. The room was smallish for the group, which I like as it makes it all feel nice and cosy, and helps focus attention. Unfortunately, there was limited ventilation, so it did get a bit stuffy through the talks. I got lots of good questions throughout the presentation. There were dead patches (combination of stuffiness and me talking for too long), but overall I was very happy with these sessions. The majority of questions came from younger students, with fewer from those in grade 9 and 10, though those seemed to be (at least) weighing up what I had to say.

I don’t recall giving the same presentation back-to-back before, and due to my talk-about-things-as-I-get-to-them style, at various stages in the second talk I lost track of whether I was repeating myself to the group. Fortunately, I had the Pathway Planning Officer present in the room (sitting among the group) who asked some useful leading questions from time to time, which seemed to work well.

Also, I had some pacman-shaped keychains from the UTas School of Computing. They were popular ;)

Feedback: see the comment here.

High School 2

After being advertised to other high schools around the state, I was contacted about visiting another one nearby. The original suggestion was that I would talk to the ICT class(es) — I pushed back against this, suggesting that it would be far better to talk to students with some interest in the industry, and this was agreed to.

The presentation was — I believe — advertised to students, and they could opt in. The group was 40+ students, mostly year 9 and 10, some 7 and 8, and included one female.

The setting was a larger area in an open-plan building, which meant attention was less focussed inwards. I would describe this talk as being “more work”. I got far fewer questions and less interaction, which resulted in me talking more. I don’t like that — it tends to be a cycle that is hard to break. That said, I still got a number of good questions. Particularly, some of the younger guys were very keen and were making games. At this school I was asked for suggestions about how to get into gamedev when you don’t have a computer or internet access outside of school.

At one stage in the presentation, I was certain that I saw a look of horror on the face of one of the staff due to something I’d said. Not sure what it was (or — in hindsight — if I’d read it correctly), but it stood out and amused me just a bit.

High School 3

Another room full of guys, opted-in, nicely structured seating, great attention, lots of questions and engagement. Grades 7–10 (iirc). Turned out that this school uses GameMaker as part of the ICT curriculum, and these were a particular eager and interested bunch.

After the presentation, one asked to show me a game he had been working on, which was absolutely fantastic to see — I got to see it in action, hear his plans for what he wanted to do with it, offer some ideas. Also, some days afterwards, one of the students sent me a thank-you card! :D

High School 4

This place was hard work. Three sessions, each to a different grade computing class (against my stated preference). Included students who were vocally opposed to being there, and consequently an environment where it was difficult to get engagement (answers to questions or questions from the audience). Sessions were held in computing classrooms, with PCs around the outside of the room, and chairs clustered (somewhat awkwardly) in the middle.

It wasn’t a total loss — there were certainly students interested in gamedev careers, and there were others that (I’m pretty sure) heard enough about what I had to say about career preparation in general that it might be of benefit.

The second session was with the grade 10 class, who were very short on questions. There were a number in this group who were clearly a lot more thoughtful and calculating — there were looks of concentration on and consideration of what I had to say. Also, the couple of teachers in the room threw in some questions to help focus the content on what was relevant to this age group — questions of subject selection for college and later came up from teachers a few times through the talk. My answer to that is do what is useful for the sort of career you want — there are potential benefits to both broader and narrower paths.

The three talks at this school were spread out over a whole school day, and the last group, at the end of the day were grade 7 and 8s. There was not a lot of focussed attention in the room. There were a number that were distracted and distracting for most of the session. In the end, it kind of devolved into me asking for games people had played recently and me providing a quick fact/anecdote/news headline about the game/mechanic/developer/development process/whatever. In hindsight, I’m not sure what it was the best way to round out the session — it kept their attention, but probably wasn’t all that useful.

There was a cool conclusion to the day — after the last session, one of the students demoed some gameplay extensions he’d made to a game he’d downloaded from @notch’s website (I think it was this). Getting to meet students with this kind of interest and enthusiasm (and this wasn’t the only time it happened) was the high point for me of the whole series of presentations.

High School 5

Final school, another three presentations. This time, I spoke to all of the grade 10 students (and one grade 9 guy who was super keen). I was a bit worried that this one would be another one stifled by objectors, but overall things went very well. The students were interested and engaged, and mostly the dynamic within the room was comfortable/amiable. It was a great place to finish.

The school has a large kitchen operated by students, and I was provided lunch from there which was also pretty cool — students eager, active and making stuff (eats!) was consistent with what I was trying to encourage and a delight to see :D

Summing up

Themes of questions did tend to vary from school to school — I do recall answering different styles of questions at different schools, though it’s hard to generalise what the differences were. I did get quite a few questions about what my favourite game is that I was not well prepared for — “I like lots of different types of games” is not a satisfying answer, even to me.

I had very little with me in terms of props — a whiteboard, upon which I wrote my name and wrote the 5 points as I went through them, and my Touchpad (containing notes, in case of catastrophic memory failure). My name — deliberately written out in full — tended to lead to some banter between myself and those gathered before the presentation regarding its pronunciation, which worked as a starting ice breaker. I did try chatting with the students beforehand in most cases to try to help lower the barrier to interaction during the talk.

Another stand-out was arriving early at one of the schools and getting to hear a couple of pieces performed by the small male choir, which was very impressive :)

Was it worth it? What was the benefit? Would I do it again? I enjoyed delivering the presentations, and the experience overall — but that wasn’t the point of the exercise. The feedback I have received is that there were students who were impacted by what I had to say — there were consequences. Students (and teachers) gained a slightly better understanding of the industry. I didn’t sugar-coat it — I presented what I think was a fair summary of risk and reward. I told no one to go into gamedev — rather, encouraged them to work for what they’re passionate about, to choose to do things that will help, and maybe to aspire a little higher than they otherwise might do. Given the chance, I would be very happy to do this sort of thing again.