Wikia

Touhou Wiki

Touhou Danmakufu: Microthreads

7,297pages on
this wiki
Talk0

Microthreads (tasks) is the very very important feature of Touhou Danmakufu. Microthreads are similar to functions and subroutines, but microthreads can be suspended. This property will let you make complex danmaku easily.

Overview of the Enemy Process

Before talking on microthreads, let's overview the entire of the process. The enemy process is abstracted as:

[Enemy] {
    [Data]
    [Process] {
        [Initialization]
        [Loop] {
            [Main]
            [Drawing]
        }
        [Finalization]
    }
}

Figure 1. Enemy process of Touhou Danmakufu

[Enemy] has [Data], which are variables directly defined in script_enemy_main.

First, the [Initialization] is executed in the [Process] of the [Enemy]. Next, the [Main] and [Drawing] are performed alternately, until the [Enemy] is defeated. Last, the [Finalization] is executed after the [Enemy] is defeated. The [Initialization], [Main], [Drawing], and [Finalization] correspond to @Initialize, @MainLoop, @DrawLoop, and @Finalize, respectively. Implementing these routines generates various danmaku in Touhou Danmakufu.

However, it is ideal that the entire picture of complex danmaku scripts is as follows:

[Enemy] {
    [Enemy Behavior] {
        [Data]
        [Process] {
            [Initialization]
            [Loop] {
                [Main]
                [Drawing]
            }
            [Finalization]
        }
    }
    [Bullet 1] {
        [Data]
        [Process] {
            [Initialization]
            [Loop] {
                [Main]
                [Drawing]
            }
            [Finalization]
        }
    }
    [Bullet 2] {
        ...
    }
    [Bullet 3] {
        ...
    }
    ...
    [Information 1] {
        ...
    }
    ...
}

Figure 2. Idial picture of the complex enemy process

That is, some bullets and some information need to be controlled at the same time as controling the enemy itself. But, in fact, the entire picture without microthreads is as following:

[Enemy] {
    [Data] {
        [Enemy behavior]
        [Bullet 1]
        [Bullet 2]
        [Bullet 3]
        ...
        [Information 1]
        ...
    }
    [Process] {
        [Initialization] {
            [Enemy behavior]
            [Bullet 1]
            [Bullet 2]
            [Bullet 3]
            ...
            [Information 1]
            ...
        }
        [Loop] {
            [Main] {
                [Enemy behavior]
                [Bullet 1]
                [Bullet 2]
                [Bullet 3]
                ...
                [Information 1]
                ...
            }
            [Drawing] {
                [Enemy behavior]
                [Bullet 1]
                [Bullet 2]
                [Bullet 3]
                ...
                [Information 1]
                ...
            }
        }
        [Finalization] {
            [Enemy behavior]
            [Bullet 1]
            [Bullet 2]
            [Bullet 3]
            ...
            [Information 1]
            ...
        }
    }
}

Figure 3. Actual picture of the complex enemy process

The processing flow of Fig. 3 is just detailed version of Fig. 1. i.e. Fig. 2 is ideal, but Fig. 3 must be emploied since there is the limitation (Fig. 1).


Microthreads

Then the ideal processing flow (Fig. 2) is really ideal? Is it impossible? In fact, the answer is almost No! The microthreads help us program as Fig. 2.

The following program is the microthread version of Touhou Danmakufu: Simple Script.

#東方弾幕風
#Title[Test Sign 'Test']
#Text[Test script]
#ScriptVersion[2]

script_enemy_main {
    let ImgBoss = "script\img\ExRumia.png";

    @Initialize {
        SetLife(2000);
        SetTimer(50);
        SetScore(1000000);

        SetMovePosition02(GetCenterX, GetClipMinY + 120, 120);
        CutIn(YOUMU, "Test Sign 'Test'", "", 0, 0, 0, 0);

        LoadGraphic(ImgBoss);
        SetTexture(ImgBoss);
        SetGraphicRect(0, 0, 64, 64);

        TNway;
    }

    @MainLoop {
        SetCollisionA(GetX, GetY, 32);
        SetCollisionB(GetX, GetY, 16);

        yield;
    }

    @DrawLoop {
        DrawGraphic(GetX, GetY);
    }

    @Finalize {
        DeleteGraphic(ImgBoss);
    }

    task TNway {
        yield;

        loop(120) { yield; }

        loop {
            loop(10) { yield; }
            CreateShot01(GetX, GetY, 5, GetAngleToPlayer, RED01, 0);
        }
    }
}

The main process is much shorter than Touhou Danmakufu: Simple Script. All the processes except setting the collision detection disappear from @MainLoop. Instead of them, yield; is written, and it seems that the disappeared process is in task TNway. The TNway is the microthread.

The TNway is called in @Initialize as:

    @Initialize {
        ...

        TNway;
    }

In case of functions and subroutines, the entire process of the TNway is completed before @Initialize ends. It's no good, since the process should be mainly performed in @MainLoop again and again.

Whereas in case of microthreads, the process of the TNway can be suspended at yield.

    task TNway {
        yield;    <- suspended!

        loop(120) { yield; }    <- 120 times suspended!

        loop {
            loop(10) { yield; }    <- 10 times suspended!
            CreateShot01(GetX, GetY, 5, GetAngleToPlayer, RED01, 0);
        }
    }

All the suspended processes are restored at yield which is called out of any microthreads. In this example, yield in @MainLoop is that.

    @MainLoop {
        SetCollisionA(GetX, GetY, 32);
        SetCollisionB(GetX, GetY, 16);

        yield;    <- restore the suspended microthreads
    }

Syntax

task (name) ( (parameters) ) {
    (statements)
}

Microthread definition is similar to the function definition. return statement can be used in microthreads, but any return values cannot be set, i.e. only return; can be used in order to exit the microthread.

yield;

yield has two behaviors. If called in a microthread, yield suspends the microthread and the processing flow returns to the calling (at first) or restoring (the other cases) point. If called out of any microthreads, yield restores all the microthreads.

The restoring sequence of the microthreads obeys the following rules:

  • When started out of any microthreads, the microthread will be restored last.
  • When started in a microthred, the microthread will be restored just before the caller microthread.

Helper Functions

wait

function wait(n) {
    loop(n) { yield; }
}

Suspend the microthread for n frames.


waitForEnemyToStop

sub waitForEnemyToStop {
    loop(GetSpeed > 0) { yield; }
}


Boolean Signal

let signal = false;

sub clearSignal { signal = false; }
sub sendSignal  { signal = true ; }

sub waitForSignal {
    while(! signal) { yield; }
}


Signal

let signal = 0;

sub clearSignal { signal = 0; }
sub sendSignal  { signal++;   }

function waitForSignal(n) {
    while(signal < n) { yield; }
}

Around Wikia's network

Random Wiki