Tuesday, 10 December 2013

Scripting with DSLs

In class we were shown a GDC talk from Mike Lewis about DSLs and AI, how to build them and integrate them into your game. Let's start with what they are.

What is a DSL?
DSL stands for Domain Specific Language and it is simply a language designed around solving a specific set of problems. On the continuum of programming languages, that range from the most generic and widely used, like C++, Java to the really specific, like SQL or Verilog you can design your DSL to be as specific as you like. Some languages are more specialized than others, some are designed to execute in specific environments, like server-based languages, and others are intended to be as widely applicable as possible.

What do they look like then?


Above is a code sample from Mike Lewis's presentation describing a fragment of a cutscene. As you can see it bears some resemblance to web-based languages like HTML or query based language like SQL so they are easy to understand and create.

DSL's resemble declarative file formats although typically remaining imperative. This not necessarily mean your DSL cannot be declarative, and there is nothing really wrong with it. DSL's can adapt to change and you can formulate them to do so.

Why are DSL's useful for AI?
There are a few core principles or tenants to keep in mind when designing a DSL that will help ensure you design it properly.

#1 - Code Cleanliness
Code cleanliness needs to be stressed here. You need to design the code in a way that the people who will be implementing your DSL think about solving the problem. Your DSL design needs to be specific enough to the problem at hand, so that it can be mapped directly onto the problem. Your code will be cleanest when implemented in the same terms that designers think about solving the problem.

#2 - Eliminating Irrelevant Concerns
Eliminate all problems that do not directly apply to the problem we need to solve. Only focus on the major concerns, looking at the big picture. Doing this can minimize bugs and save valuable time in debugging.

#3 - Remove Excess Overhead
Productivity can be maximized by removing details and features that don't pertain to the problem at hand. Strip away all the excess and drill down to the core of what you're trying to solve without worrying about the clutter of other less pertinent issues.

What makes this useful?
AI work typically consists of solving a limited amount of problems, using already tried and true techniques. Techniques that you can implement in your own fashion, but basically solutions that have already been though out for you , for your problem.


What are the perks of using DSL's?
Not having to use C++ allows us some serious benefits. There is no longer a need to manually manage memory, which can be a difficult thing for Jr. Programmers. There is also no need to learn complex syntax, as the syntax is designed by yourself to be as logical and plainspoken as possible. Finally you avoid most complications like undefined behavior which can occur in programming AI, something that makes the programming of AI so difficult.

Overall it makes the code more accessible to those not as adept, like designers and junior coders, reduce the chance to cause severe, game-breaking bugs in code, and be more productive. Freedom from C++!

There are common misconceptions when it comes to DSL development, like compilers being overly complicated, learning to use a interpreter, worrying about garbage collection etc., but this is not the case. DSL's are, by definition, designed to simple and to the point. They omit the major issues the concern the more generic and massive languages of C++, and require far less infrastructure to be deployed effectively.

You don't need all those complex parsers, compilers or virtual machines or even adherence to regular syntax. DSL's are and should be easy! The syntax is simple and intuitive, something you would logically say in pseudo code. You can even use existing file formats like XML or JSON without the need for the extraneous compiler layers. The problem space is limited and execution code need not be complex, you are developing for a limited problem after all. You can even grab existing data driven code that's already present in your engine and build on that.

The fundamental goal then, is to take all the data and logic out of core engine code and move it into the DSL. You are essentially just taking data driven programming to the next logical level, isolating it in its own language, its own problem space. The core thing to remember is, you want to do the simplest thing that can possibly work, without excess complexity.

How do we build and deploy a DSL?
The process of building and implementing a DSL can be broken down into four major chunks which involve identifying core problems, selecting algorithms and solutions, breaking down solutions and implementing the concepts.


Core problems for AI can be things like pathfinding, whether it be nav mesh or goal oriented, responding to dynamic game situations once they happened. Anticipating dynamic situations is also tricky as you need to plan for the unpredictability of human players. So what kind of solution do we choose?

As per our previous statement we don't need to do anything special or extra, as we want to keep the solution simple. We choose what we would normally implement anyways or as the design requires. To determine what the design requires though, we need to break it down into common concerns and issues, and decide what decision making logic we will be using. Of particular importance when planning out AI are branch points. Branch points will essentially be where DSLs should be placed.


There is no need to make code over complicated to read and understand and we can apply this methodology of thinking to designing the syntax. For example if we wanted to instruct the AI to begin pathfinding we could tell it, find_path(), or reading in a state, get_player_health(), or doing computations such as: calculate_damage().

Implementation
To implement the DSL we must remember first and foremost, to keep syntax simple and clean, and if not use existing file formats like XML or JSON. Syntax needs to be clean enough to parse very, very easily using again just regular expressions, almost things that you would say in conversation. A good example of this can be seen below:



The above code is much easier to parse than standard C++ syntax. It maps the logic directly onto the right concept and in doing so minimizes potential for bugs due to your understanding and clarity of the command. This in essence is what the implementation of a DSL is trying to achieve, something that could be understood by those not familiar with computer code.

Who benefits from this?
Basically everyone! Programmers can enjoy a much safer environment to work in, worrying less about debugging and more on actual development. This same idea allows junior members, or those not to comfortable with the code, to do critical AI work and contribute more on the project. It even enables those that would not originally program, like designers to write logic directly themselves, saving the time of moving the code back and forth between designer and programmer, cutting out the middleman.

When it comes to finally reviewing the code, it makes it easier for debuggers to understand what you're getting at and it means that there will be a faster turnaround time between QA and code improvements.


Remember, to have DSLs working properly in your project you need to adhere to a few core pillars. Keep the design of the code as close to the design of solving the problem as possible, so when you go to map it, it does so exactly. If all done correctly the process will be easy and rewarding, and you'll have yourself a great working AI.



No comments:

Post a Comment