Skip to content
Thin Eureka edited this page Jun 7, 2017 · 49 revisions

Asyn-script is a programming language I invented to solve the difficulties of programming that arise in scenarios like the following one.

Assume that we are programming in C++, and we need to process tasks that can be categorized into two types. The first type includes any task which can be implemented as a C++ function call that carries out the task and returns immediately to the caller. For example, making a character in a game visible belongs to this case, you just need to modify a single property, which probably is a Boolean value and return. The second type is a task that can't simply be implemented as a blocking function call in a single-thread context. For example, making a character fading in during a period of time is the second case. Because you need to render the character, and during the fading period, there are other running tasks need to handle as well. So unless a separate thread is spawned to execute the task, you can't just do all the fading process in a single function then returns to the caller when the task is complete. Otherwise what you get is just a freeze on the screen. To properly model this behavior, you usually need an additional "update" function to modify the alpha property of the character according to how much time has elapsed each frame when the update function is being called. This way of addressing the second type tasks can be witnessed everywhere such tasks are encountered.

Now here comes the problem. Simple things that involve only processing of a series of type I tasks just become complicated when some tasks are replaced by tasks of 2nd type. To illustrate my point, suppose we're writing a command line program that logs in to a server. And we demonstrate the process in lua-like pseudo codes.

function log_in(device_id)
    var success, user_id, access_token = platform_login(device_id)
    if (~success) return false
    var success, session_id , game_server_ip = user_server_login(user_id, access_token)
    if (~success) return false
    var success, player_info = game_server_login(session_id, game_server_ip, user_id)
    if (~success) return false
    return true, player_info
end

Now consider that we need to replace it with a GUI version. It means that we have to display the progress in GUI panels, and we're not allowed to block the program when we do the platform_login, login_server_login, or game_server_login tasks. Otherwise the user is prevented from doing operations during that process which would takes pretty long time. So these three tasks become type II tasks, which can't be implemented as blocking functions. As a result, what implemented in a single function log_in would have to be scattered in several places like the callbacks of result notification of thees tasks. There's nothing wrong with this approach, other than that we can't represent a simple logic in code directly. Now imagine how the complexity is multiplied when we have to deal with different results of the tasks, add retry mechanism into the process, display animations during login progress.

Even if that didn't bother you, think about programming a MMORPG, you need to model a character's behaviors of different hierarchies like playing walking animation, walking to certain destination, fighting with monsters and accepting and completing a request from NPC. You certainly need to encapsulate each task into an independent abstraction to avoid code-coupling and to reuse codes. In reality, a poor design game code would not do anything like that, they just write the codes brainlessly, the codes jump from callback to callback. And in one with good design, you can see a design pattern called behavior tree come to rescue.

Neither solution satisfies me, the first one is subject to changes, the codes are poorly readable and have to be switched among different places even if trivial changes are to be made. And behavior tree is rather cumbersome for representing logic. The ideal solution I try to find should follow a standard Bjarne Stroustrup advocated in his 4th edition The C++ Programming Language. The following paragraph is extracted from his book.

The general ideals for design and programming can be expressed simply:
• Express ideas directly in code.
• Express independent ideas independently in code.
• Represent relationships among ideas directly in code.
• Combine ideas expressed in code freely – where and only where combinations make sense.
• Express simple ideas simply.

Suppose we're using C++, in either solution, we can't express the logic in C++ codes directly or we need to resort to cumbersome behavior tree structures that can't express simple ideas simply. The ideal representation of simple logical relation is a function which executes the tasks in sequence or use loop(for or while), conditional(if) statements to control the flow of the codes, and function calls to hide sub-tasks whose implementation have nothing to do with the main logic. But since the introduction of the type II tasks, we can't do that in a single function any more. We think in a simple way, but we can't write codes in C++ directly in the same way. Because call stack is not preserved after function returns unless co-routine is directly supported in C++ or use a different thread, which we can see is obviously inappropriate, the relationship can't be represented in a C++ function.

The essential of this kind of problem is that the relationship we try to express is one level higher than pure C++ code can provides, the process lasts a period of time during which time, other task are being processed as well. For a better code design, we should not mix codes of different levels. After several tries looking for a ideal solution to this problem. I finally recognized that a virtual machine and a script language is needed to represent this higher level of logic. And luckily I created a such a language that works well with C++, because it's provided in the form of C++ member function of a class that represents a function and a virtual machine during its compile time. This language is asyn-script. It's actually C++ code, and can be easily debugged and compiled along with other C++ codes. Actually, with the help of anonymous function and macro definition, the debugging of asyn-script code is far more easy than its pure C++ counterpart since the higher level is exposed, whereas in the pure C++ version, you just get the same function calling stacks no matter where the logic has reached in the higher level.

This idea had been conceived for a very long time, but it had not been implemented until Feb 10, 2016 because I was always busy with projects from work, until the new C++ language features provided me insights and tools for implementing a simpler grammar of the script language, and the classical book of Bjarne Stroustrup inspired me how to do proper designs by making choices of abandoning unnecessary features. The codes were published on my personal site hosted by Google the day I started and finished coding the virtual machine.

You can see how Asyn-script is used to implement the login progress represented in previous example in the demo DemoLogin. Please notice that it really doesn't matter how long the task is being executed. If the task returns immediately, the virtual machine will go on processing the next instruction. If the task takes a period of time to process, the code doesn't block. Because it's run in a virtual machine, when a task is not done, it will continue from where it returns last time when the virtual machine is next time invoked. The code remains the same no matter the task is of type I or type II. It achieves this code-stability because the higher level of logic is isolated and extracted into asyn-script instead of pure C++ codes. This part of code really resembles the way how we think in human-natural language. It's not subject to changes about how long a task needs to be executed. Just the logic really matters.

With asyn-script, many design patterns are better supported. I intend to explain how this language can be harnessed to write clear and loosely-coupled codes, how it can be used in Object-oriented programming to make a whole progress other than a instant-return function virtual, thus much more powerful Polymorphism can be achieved. I plan to write a tutorial explaining the language features of asyn-script, the implementation of the virtual machine, how to apply the features to write better C++ code and how to debug the code together with other C++ code. Both English and Chinese versions will be supported when I have enough time to write the tutorial. So please stay tuned.

FAQ

Clone this wiki locally