summaryrefslogtreecommitdiff
path: root/script-beta/proto/proto.lua
blob: b70cc451f437b76c52879c14945611707b6773f6 (plain)
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
local subprocess = require 'bee.subprocess'
local util       = require 'utility'
local await      = require 'await'
local pub        = require 'pub'
local jsonrpc    = require 'jsonrpc'
local ErrorCodes = require 'define.ErrorCodes'
local timer      = require 'timer'

local reqCounter = util.counter()

local m = {}

m.ability = {}
m.waiting = {}
m.running = {}

function m.getMethodName(proto)
    if proto.method:sub(1, 2) == '$/' then
        return proto.method:sub(3), true
    else
        return proto.method, false
    end
end

function m.on(method, callback)
    m.ability[method] = callback
end

function m.response(id, res)
    if id == nil then
        log.error('Response id is nil!', util.dump(res))
        return
    end
    -- res 可能是nil,为了转成json时保留nil,使用 container 容器
    local data = util.container()
    data.id     = id
    data.result = res
    local buf = jsonrpc.encode(data)
    --log.debug('Response', id, #buf)
    io.stdout:write(buf)
end

function m.responseErr(id, code, message)
    if id == nil then
        log.error('Response id is nil!', util.dump(message))
        return
    end
    local buf = jsonrpc.encode {
        id    = id,
        error = {
            code    = code,
            message = message,
        }
    }
    --log.debug('ResponseErr', id, #buf)
    io.stdout:write(buf)
end

function m.notify(name, params)
    local buf = jsonrpc.encode {
        method = name,
        params = params,
    }
    --log.debug('Notify', name, #buf)
    io.stdout:write(buf)
end

function m.awaitRequest(name, params)
    local id = reqCounter()
    local buf = jsonrpc.encode {
        id     = id,
        method = name,
        params = params,
    }
    --log.debug('Request', name, #buf)
    io.stdout:write(buf)
    return await.wait(function (waker)
        m.waiting[id] = waker
    end)
end

function m.doMethod(proto)
    local method, optional = m.getMethodName(proto)
    local abil = m.ability[method]
    if not abil then
        if not optional then
            log.warn('Recieved unknown proto: ' .. method)
        end
        if proto.id then
            m.responseErr(proto.id, ErrorCodes.MethodNotFound, method)
        end
        return
    end
    if proto.id then
        m.running[proto.id] = method
    end
    await.create(function ()
        log.debug('Start method:', method)
        local clock = os.clock()
        local ok = true
        local res
        -- 任务可能在执行过程中被中断,通过close来捕获
        local response <close> = util.defer(function ()
            local passed = os.clock() - clock
            if passed > 0.2 then
                log.debug(('Method [%s] takes [%.3f]sec.'):format(method, passed))
            end
            log.debug('Finish method:', method)
            if not proto.id then
                return
            end
            if ok then
                m.response(proto.id, res)
            else
                m.responseErr(proto.id, ErrorCodes.InternalError, res)
            end
            m.running[proto.id] = nil
        end)
        ok, res = xpcall(abil, log.error, proto.params)
    end)
end

function m.doResponse(proto)
    local id = proto.id
    local waker = m.waiting[id]
    if not waker then
        log.warn('Response id not found: ' .. util.dump(proto))
        return
    end
    m.waiting[id] = nil
    if proto.error then
        log.warn(('Response error [%d]: %s'):format(proto.error.code, proto.error.message))
        return
    end
    waker(proto.result)
end

function m.listen()
    subprocess.filemode(io.stdin,  'b')
    subprocess.filemode(io.stdout, 'b')
    io.stdin:setvbuf  'no'
    io.stdout:setvbuf 'no'
    pub.task('loadProto')
    timer.loop(10, function ()
        for id, name in pairs(m.running) do
            m.responseErr(id, ErrorCodes.InternalError, ('Task [%s] failed unexpected!'):format(name))
        end
    end)
end

return m