Jump to content

  • Log In with Google      Sign In   
  • Create Account

Banner advertising on our site currently available from just $5!


1. Learn about the promo. 2. Sign up for GDNet+. 3. Set up your advert!


ApochPiQ

Member Since 17 Jul 2002
Offline Last Active Today, 11:33 AM

Topics I've Started

The Abominable Amalgam

11 November 2014 - 05:59 PM

I just wrote a tiny side-project thingy that used all of the following in one program:
  • Code compilation via libclang
  • Execution of generated code in-process
  • Dynamic link libraries invoked via parsing a string
  • Coroutines (Win32 fiber API)
  • longjmp
  • TerminateThread()
  • Thread-local attributes on static class members
  • A shitload of void*
I'm sure there's other terrible things in there. I just needed a confessional to get it off my chest.



I am an awful programmer.

Code organization without the limitations of files

10 September 2014 - 01:41 PM

I've been contemplating an interesting phenomenon recently, some of which has spilled out of my brain into my journal here on GDNet, but mostly it's been a lot of quiet brain-churning.

First a bit of background. Suppose we have a large project, like a decent sized game, and we need to explore the code. Consider the first time you dropped into a large, unfamiliar codebase, and what kinds of spelunking was necessary to learn how all the pieces fit together.

Usually, code is organized into conceptual units which I'll call modules. Modules consist of code units such as data structures, functions, type definitions, global variables/constants, and so on. These may or may not be able to be broken into namespaces, which are typically orthogonal to modules in terms of language semantics, although some languages treat modules and namespaces as identical concepts.

The real question is, how do we lay out all this stuff on disk? Traditionally we write code in exactly one place in exactly one file. This is great for some things, like diffs and version control, but sucks for other things - like discoverability.

Going back to the scenario from above, think about what happens when you get a project that contains a lot of code. Most of that code does not fall into exactly one category. You may have UI, graphics, physics, AI, audio, game-specific logic, and so on.


Now suppose I want to view all code related to casting a fireball spell in this particular game - head to toe, from the UI button that triggers the spell to the game logic that handles it to the graphics code that renders it. Throw in audio effects for good measure.

How do I see all this stuff in the traditional structure? I open thirty different files, each of which might have two or three code units I actually care about.

Now I need to add some logic about fireballs, but only fireballs, and it has to do with graphics and audio. I need to distribute my code changes across at least two files. And what if I have something that is related to both fireballs and lightning, but not much else? What file does it go into?


This all strikes me as extraordinarily wasteful (in terms of programmer time and energy). I'm curious if there is a better way to allow for organizing code, so that this kind of scenario goes away entirely. There are other benefits I can imagine to getting away from the 1:1 file religion, but they're mostly side effects.

The real question is: can we invent a way to think of and view code that elegantly solves this kind of problem?

Isn't "delete" so pesky to type? Wish there was a better way?

01 August 2014 - 01:09 PM

In C++, we know that memory management can be a large manual burden. Sometimes, though, it's just unavoidable, and we have to use new and delete.

A large part of the manual burden, of course, is actually typing delete, because it takes sooooo loooooong and is just annoying.


Thankfully, C++ has some special and little-known syntax to solve this headache! Suppose you have pointers (allocated via new, of course) named Foo, Bar, and Baz. All you need to do is this:

delete Foo, Bar, Baz;

Enjoy!

GDC 2014

14 March 2014 - 05:21 PM

I know there's generally mixed feelings about GDC around here, but The Time is Nigh™ and I haven't seen a thread yet, so here it is.

 

Who's going to be around this year?


Towards better error handling constructs

08 February 2014 - 05:17 PM

We all know about the eternal war between the Big Two error handling philosophies.


From Ages Immemorial we have the return-code method:

enum ReturnCode
{
    RETURN_CODE_SUCCESS,
    RETURN_CODE_BARF,
    RETURN_CODE_VOMIT,
};

ReturnCode MyFallibleFunction(int param1, const std::string& param2)
{
    // Do stuff

    // Ooops!
    return RETURN_CODE_BARF;
}
This works fine if your function only returns a result code; if it needs to have a payload as well, then you're stuck with out-parameters or other nasty hacks.


Therefore, from Slightly More Modern Times we have the exception method:

void MyFallibleFunction(int param1, const std::string& param2)
{
    // Do stuff

    // Ooops!
    throw BarfException();
}
This is nice because you can return the payload you want normally, and even attach arbitrary additional state data to the exception object itself. Unfortunately, exceptions are complicated and hard to get right, even in managed languages. Implementing exceptions is a major burden on language designers as well.


Of course there are other methods in use, such as multiple-return (return a payload in one "slot" and a success/fail code in another slot - popular in Lua, Go, etc.), discriminated-union-return (return an algebraic sum type that can carry either a success result or a failure code - popular in several functional languages), and so on. There's even the ever-mystical "continuation" method which is something like exception-style stack unwinding on steroids. (Check it out if you're not familiar with continuations, they're a damned powerful but really tricky concept that can be, at turns, very nice to have and infinitely frustrating to debug.)

A notable alternative that is popular in JavaScript (and probably other similar languages) is lambda-style handlers wherein I pass a function two lambdas, one that runs on success, and one that runs on failure. This is kind of a nice idea, but it turns into a soup of nested lambdas in complex scenarios, and gets ugly really, really fast.


I was thinking earlier about how I would implement error handling in an ideal scenario, and came up with a list of mandatory functionality I'd want involved:
  • Let me return a payload trivially - and with zero runtime overhead - in success cases
  • Allow arbitrarily rich "failure" objects/codes/etc. so I can be very precise about what went wrong
  • Complete static type safety
  • Allow arbitrary handling logic from the caller or callee when errors occur
  • Keep error processing logic close to, but not intermingled with, success-case code
Some of these are obviously easier than others.

Here's what I came up with:

entrypoint :
{
    protect
    {
        print(FallibleFunction(42, "Test"))
    }
    with task
    {
        Barf : { print("Function barfed.") }

        Vomit : string reason -> integer fallback = 0
        { print("Vomit! Falling back to 0 becuase: " ; reason) }
    }
}

FallibleFunction : integer p1, string p2 -> string ret = ""
{
    if(p1 > 100)
    {
        panic => Barf()
    }
    
    while(p1 > 20)
    {
        p1 = (panic => Vomit("Number slightly too high"))
    }

    ret = p2 ; " ... " ; cast(string, p1)
}

In this example, we set up a "fallible" function which accepts an integer and a string. We then call this function from inside a "protect" block, which is followed by a task (think actor) which can accept messages.

(NB: think of foo=>Bar() as syntax for "send the Bar message to the foo task." In this case, panic is a special task alias for the "nearest" protector task that can handle the given message.)

Inside the function, we check if the integer passed is "very large" in which case we just "barf." This is equivalent to an unrecoverable error. Then, we set up a recoverable error if the integer is "slightly" too high. In the recoverable case, we actually fire a message to the protector task and use its return value to change the parameter. This repeats until a sane value is passed in; for sake of simplicity, this can loop infinitely, but more complex and realistic handling would just obscure the example.

Finally, the function constructs a string based on its input parameters, and returns it.



I like this mechanism. It allows trivial returns of payloads in success cases, and even allows totally unguarded execution, similar to exceptions and stack unwinding, if I (as the programmer) so desire. Failure messages can pass arbitrarily rich details to the protector task. Enforcing type safety and even static error-robustness checking is possible, albeit not necessarily trivial. We see a perfect example of caller and callee interacting to correct the "vomit" condition. Last but not least, I could hypothetically use a non-inline task if I wanted to, allowing reuse of error handling logic, or separation of concerns between success and failure paths, etc.

This feels to me like the best of all possible worlds, but I'm curious if it even makes sense to anyone else, or if someone has a better idea for how to handle error situations in code. Keep in mind I'm not looking for a solution to bolt into an existing language so much as a theoretical ideal.


Thoughts?

PARTNERS