File: chainable.rb

package info (click to toggle)
ruby-delayer-deferred 2.2.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, forky, sid, trixie
  • size: 308 kB
  • sloc: ruby: 1,650; sh: 4; makefile: 2
file content (155 lines) | stat: -rw-r--r-- 4,584 bytes parent folder | download | duplicates (2)
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# -*- coding: utf-8 -*-
require "delayer/deferred/deferredable/awaitable"
require "delayer/deferred/deferredable/graph"
require "delayer/deferred/deferredable/node_sequence"

module Delayer::Deferred::Deferredable
  module Chainable
    include Awaitable
    include Graph
    include NodeSequence

    attr_reader :child

    # このDeferredが成功した場合の処理を追加する。
    # 新しいDeferredのインスタンスを返す。
    # このメソッドはスレッドセーフです。
    # TODO: procが空のとき例外を発生させる
    def next(&proc)
      add_child(Delayer::Deferred::Chain::Next.new(&proc))
    end
    alias deferred next

    # このDeferredが失敗した場合の処理を追加する。
    # 新しいDeferredのインスタンスを返す。
    # このメソッドはスレッドセーフです。
    # TODO: procが空のとき例外を発生させる
    def trap(&proc)
      add_child(Delayer::Deferred::Chain::Trap.new(&proc))
    end
    alias error trap

    # この一連のDeferredをこれ以上実行しない。
    # このメソッドはスレッドセーフです。
    def cancel
      change_sequence(:genocide) unless spoiled?
    end

    def has_child?
      child ? true : false
    end

    # 子を追加する。
    # _Delayer::Deferred::Chainable_ を直接指定できる。通常外部から呼ぶときは _next_ か _trap_ メソッドを使うこと。
    # このメソッドはスレッドセーフです。
    # ==== Args
    # [chainable] 子となるDeferred
    # ==== Return
    # 必ず _chainable_ を返す
    # ==== Raise
    # [Delayer::Deferred::SequenceError]
    #   既に子が存在している場合
    def add_child(chainable)
      change_sequence(:get_child) do
        chainable.parent = self
        @child = chainable
      end
    end

    # 子が追加された時に一度だけコールバックするオブジェクトを登録する。
    # observerと言っているが、実際には _Delayer::Deferred::Worker_ を渡して利用している。
    # このメソッドはスレッドセーフです。
    # ==== Args
    # [observer] pushメソッドを備えているもの。引数に _@child_ の値が渡される
    # ==== Return
    # self
    def add_child_observer(observer)
      change_sequence(:gaze) do
        @child_observer = observer
      end
      self
    end

    def awaited
      @awaited ||= [].freeze
    end

    def has_awaited?
      not awaited.empty?
    end

    def add_awaited(awaitable)
      @awaited = [*awaited, awaitable].freeze
      self
    end

    # activateメソッドを呼ぶDelayerジョブを登録する寸前に呼ばれる。
    def reserve_activate
      change_sequence(:reserve)
    end

    def enter_pass
      change_sequence(:pass)
    end

    def exit_pass
      change_sequence(:resume)
    end

    protected

    # 親を再帰的に辿り、一番最初のノードを返す。
    # 親が複数見つかった場合は、それらを返す。
    def ancestor
      if @parent
        @parent.ancestor
      else
        self
      end
    end

    # cancelとかデバッグ用のコールグラフを得るために親を登録しておく。
    # add_childから呼ばれる。
    def parent=(chainable)
      @parent = chainable
    end

    private

    def call_child_observer
      if has_child? and defined?(@child_observer)
        change_sequence(:called)
        @child_observer.push(@child)
      end
    end

    def on_sequence_changed(old_seq, flow, new_seq)
      case new_seq
      when NodeSequence::BURST_OUT
        call_child_observer
      when NodeSequence::GENOCIDE
        @parent.cancel if defined?(@parent) and @parent
      when NodeSequence::RESERVED_C, NodeSequence::RUN_C, NodeSequence::PASS_C, NodeSequence::AWAIT_C, NodeSequence::GRAFT_C
        if !has_child?
          notice "child: #{@child.inspect}"
          raise Delayer::Deferred::SequenceError.new("Sequence changed `#{old_seq.name}' to `#{flow}', but it has no child")
        end
      end
    end

    # ノードの名前。サブクラスでオーバライドし、ノードが定義されたファイルの名前や行数などを入れておく。
    def node_name
      self.class.to_s
    end

    def graph_mynode
      if defined?(@seq_logger)
        label = "#{node_name}\n(#{@seq_logger.map(&:name).join('→')})"
      else
        label = "#{node_name}\n(#{sequence.name})"
      end
      "#{__id__} [shape=#{graph_shape},label=#{label.inspect}]"
    end

  end
end