Featured image of post 第6回-順番待ちキュー - PerlとMooで航空管制シミュレーターを作ろう

第6回-順番待ちキュー - PerlとMooで航空管制シミュレーターを作ろう

複数機が着陸待ちの場合、順番に処理するキュー機能を追加。先着順で公平に処理します。

順番待ちキュー

前回の振り返り

前回は、滑走路クラスを分離してリソース管理を実装しました。

しかし、滑走路が使用中のとき航空機は待機するだけで、二度と着陸できませんでした。今回は、着陸待ちのキューを実装して、順番に処理できるようにします。

キューとは

キュー(Queue)は「先入れ先出し」(FIFO: First In, First Out)のデータ構造です。先に並んだものが先に処理されます。

	flowchart LR
    subgraph キュー
        direction LR
        A[JAL123] --> B[ANA456] --> C[SKY789]
    end
    D[次に処理] --> A
    C --> E[新規追加]

空港の着陸待ちも同じです。先に着陸を要求した航空機から順番に着陸します。

管制塔にキューを追加

管制塔にwaiting_queueを追加し、着陸待ちの航空機を管理します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package ControlTower {
    use Moo;

    has aircrafts => (is => 'ro', default => sub { [] });
    has runway => (is => 'ro', required => 1);
    has waiting_queue => (is => 'ro', default => sub { [] });

    sub register($self, $aircraft) {
        push @{$self->aircrafts}, $aircraft;
        $aircraft->tower($self);
        say "管制塔: " . $aircraft->flight_number . "を登録しました";
    }

    sub request_landing($self, $aircraft) {
        if (!$self->runway->is_available) {
            say "管制塔: " . $aircraft->flight_number . 
                "、滑走路使用中です。キューに追加します";
            push @{$self->waiting_queue}, $aircraft;
            $aircraft->receive_clearance(0);
            return;
        }
        $self->_grant_landing($aircraft);
    }

    sub _grant_landing($self, $aircraft) {
        $self->runway->occupy($aircraft);
        say "管制塔: " . $aircraft->flight_number . 
            "、着陸を許可します";
        $aircraft->receive_clearance(1);
    }

    sub notify_landed($self, $aircraft) {
        $self->runway->release;
        say "管制塔: " . $aircraft->flight_number . 
            "の着陸を確認";
        $self->_process_queue;
    }

    sub _process_queue($self) {
        if (@{$self->waiting_queue} == 0) {
            say "管制塔: 待機中の航空機はありません";
            return;
        }
        my $next = shift @{$self->waiting_queue};
        say "管制塔: 次は" . $next->flight_number . "です";
        $self->_grant_landing($next);
    }
}

ポイント:

  • waiting_queue: 着陸待ちの航空機を格納する配列
  • _process_queue: 着陸完了後に次の航空機を処理

完成コード

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#!/usr/bin/env perl
use v5.36;

package Aircraft::Role {
    use Moo::Role;
    requires 'request_landing';
    requires 'receive_clearance';
    has tower => (is => 'rw');
}

package Runway {
    use Moo;

    has name => (is => 'ro', required => 1);
    has occupied_by => (is => 'rw', default => undef);

    sub is_available($self) {
        return !defined $self->occupied_by;
    }

    sub occupy($self, $aircraft) {
        $self->occupied_by($aircraft);
        say "滑走路" . $self->name . ": " . 
            $aircraft->flight_number . "が使用開始";
    }

    sub release($self) {
        my $aircraft = $self->occupied_by;
        $self->occupied_by(undef);
        say "滑走路" . $self->name . ": " . 
            $aircraft->flight_number . "が使用終了";
    }
}

package ControlTower {
    use Moo;

    has aircrafts => (is => 'ro', default => sub { [] });
    has runway => (is => 'ro', required => 1);
    has waiting_queue => (is => 'ro', default => sub { [] });

    sub register($self, $aircraft) {
        push @{$self->aircrafts}, $aircraft;
        $aircraft->tower($self);
        say "管制塔: " . $aircraft->flight_number . "を登録しました";
    }

    sub request_landing($self, $aircraft) {
        if (!$self->runway->is_available) {
            say "管制塔: " . $aircraft->flight_number . 
                "、滑走路使用中です。キューに追加します";
            push @{$self->waiting_queue}, $aircraft;
            $aircraft->receive_clearance(0);
            return;
        }
        $self->_grant_landing($aircraft);
    }

    sub _grant_landing($self, $aircraft) {
        $self->runway->occupy($aircraft);
        say "管制塔: " . $aircraft->flight_number . 
            "、着陸を許可します";
        $aircraft->receive_clearance(1);
    }

    sub notify_landed($self, $aircraft) {
        $self->runway->release;
        say "管制塔: " . $aircraft->flight_number . 
            "の着陸を確認";
        $self->_process_queue;
    }

    sub _process_queue($self) {
        if (@{$self->waiting_queue} == 0) {
            say "管制塔: 待機中の航空機はありません";
            return;
        }
        my $next = shift @{$self->waiting_queue};
        say "管制塔: 次は" . $next->flight_number . "です";
        $self->_grant_landing($next);
    }
}

package Aircraft {
    use Moo;
    with 'Aircraft::Role';

    has flight_number => (is => 'ro', required => 1);

    sub request_landing($self) {
        say $self->flight_number . ": 着陸許可をリクエストします";
        $self->tower->request_landing($self);
    }

    sub receive_clearance($self, $cleared) {
        if ($cleared) {
            say $self->flight_number . ": 着陸します";
            $self->tower->notify_landed($self);
        } else {
            say $self->flight_number . ": 待機します";
        }
    }
}

# 滑走路と管制塔を作成
my $runway = Runway->new(name => 'A');
my $tower = ControlTower->new(runway => $runway);

# 航空機を作成して登録
my $flight1 = Aircraft->new(flight_number => 'JAL123');
my $flight2 = Aircraft->new(flight_number => 'ANA456');
my $flight3 = Aircraft->new(flight_number => 'SKY789');

$tower->register($flight1);
$tower->register($flight2);
$tower->register($flight3);

say "=== 全機が同時に着陸をリクエスト ===";

# 全機が同時に着陸をリクエスト
$flight1->request_landing;
say "---";
$flight2->request_landing;
say "---";
$flight3->request_landing;

実行結果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
管制塔: JAL123を登録しました
管制塔: ANA456を登録しました
管制塔: SKY789を登録しました
=== 全機が同時に着陸をリクエスト ===
JAL123: 着陸許可をリクエストします
滑走路A: JAL123が使用開始
管制塔: JAL123、着陸を許可します
JAL123: 着陸します
滑走路A: JAL123が使用終了
管制塔: JAL123の着陸を確認
管制塔: 待機中の航空機はありません
---
ANA456: 着陸許可をリクエストします
滑走路A: ANA456が使用開始
管制塔: ANA456、着陸を許可します
ANA456: 着陸します
滑走路A: ANA456が使用終了
管制塔: ANA456の着陸を確認
管制塔: 待機中の航空機はありません
---
SKY789: 着陸許可をリクエストします
滑走路A: SKY789が使用開始
管制塔: SKY789、着陸を許可します
SKY789: 着陸します
滑走路A: SKY789が使用終了
管制塔: SKY789の着陸を確認
管制塔: 待機中の航空機はありません

この例では着陸が即座に完了するため、キューが溜まっていません。実際の運用では着陸に時間がかかるため、複数の航空機がキューで待機することになります。

キューの動作確認

キューが正しく動作することを確認するために、着陸完了を遅延させてみましょう。

1
2
3
4
5
6
7
8
9
# 滑走路が使用中の状態を作る
$runway->occupy($flight1);  # 手動で占有

# その状態で2機がリクエスト
$flight2->request_landing;  # キューに追加
$flight3->request_landing;  # キューに追加

# flight1が着陸完了
$tower->notify_landed($flight1);  # キューから処理開始

今回のまとめ

今回は、着陸待ちのキューを実装しました。

  • 滑走路が使用中なら、waiting_queueに追加
  • 着陸完了後、キューから次の航空機を処理
  • FIFO(先入れ先出し)で公平に処理

しかし、現実の航空管制では「燃料切れ」などの緊急事態が発生することがあります。次回は、緊急事態の航空機を優先する機能を追加します。

comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。