├── README.md ├── busDemo3.gif ├── client ├── client.lua ├── loader.lua ├── serialize.lua └── upload.sh ├── comp.gif ├── recipe-help.png ├── server ├── CPPImpl │ ├── .gitignore │ ├── Actions.cpp │ ├── Actions.h │ ├── CMakeLists.txt │ ├── Entry.cpp │ ├── Factory.cpp │ ├── Factory.h │ ├── Item.cpp │ ├── Item.h │ ├── Overload.h │ ├── Processes.cpp │ ├── Processes.h │ ├── Serialize.cpp │ ├── Serialize.h │ ├── Server.cpp │ ├── Server.h │ ├── Storages.cpp │ ├── Storages.h │ └── WeakCallback.h ├── RustImpl │ ├── Cargo.toml │ ├── rustfmt.toml │ └── src │ │ ├── access.rs │ │ ├── action.rs │ │ ├── config.rs │ │ ├── config_util.rs │ │ ├── factory.rs │ │ ├── item.rs │ │ ├── lua_value.rs │ │ ├── main.rs │ │ ├── process │ │ ├── blocking_fluid_output.rs │ │ ├── blocking_output.rs │ │ ├── buffered.rs │ │ ├── crafting_grid.rs │ │ ├── fluid_slotted.rs │ │ ├── manual_ui.rs │ │ ├── misc.rs │ │ ├── mod.rs │ │ ├── multi_inv_slotted.rs │ │ ├── reactor.rs │ │ ├── redstone.rs │ │ ├── scattering.rs │ │ └── slotted.rs │ │ ├── recipe.rs │ │ ├── server.rs │ │ ├── side.rs │ │ ├── storage │ │ ├── chest.rs │ │ ├── drawer.rs │ │ ├── me.rs │ │ └── mod.rs │ │ └── util.rs └── forward.sh └── workbench2.gif /busDemo3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb0124/OCRemote/38218cf5367a7f8e9cd8799cff14649a2712fdec/busDemo3.gif -------------------------------------------------------------------------------- /client/client.lua: -------------------------------------------------------------------------------- 1 | local serverAddr, serverPort, clientName, resX, resY = ... 2 | 3 | local encode 4 | (function() 5 | local encMap = { 6 | ['nil'] = function(x) 7 | return '!' 8 | end, 9 | number = function(x) 10 | return string.format("#%.17g@", x) 11 | end, 12 | string = function(x) 13 | return '@' .. string.gsub(x, '@', '@.') .. '@~' 14 | end, 15 | boolean = function(x) 16 | if x then return '+' else return '-' end 17 | end, 18 | table = function(x) 19 | local result = '=' 20 | for k, v in pairs(x) do 21 | result = result .. encode(k) .. encode(v) 22 | end 23 | return result .. '!' 24 | end 25 | } 26 | function encode(x) 27 | return encMap[type(x)](x) 28 | end 29 | end)() 30 | 31 | local function decode(cb) 32 | local s, sTable 33 | local function sNumber() 34 | local p, data = s, '' 35 | function s(x) 36 | local pos = string.find(x, '@') 37 | if pos then 38 | s = p 39 | s(tonumber(data .. string.sub(x, 1, pos - 1)), string.sub(x, pos + 1)) 40 | else 41 | data = data .. x 42 | end 43 | end 44 | end 45 | local function sString() 46 | local p, data, escape = s, '', false 47 | function s(x) 48 | for i = 1, #x do 49 | local now = string.sub(x, i, i) 50 | if escape then 51 | if now == '.' then 52 | data = data .. '@' 53 | escape = false 54 | elseif now == '~' then 55 | s = p 56 | return s(data, string.sub(x, i + 1)) 57 | else 58 | error('unknown escape: ' .. now) 59 | end 60 | elseif now == '@' then 61 | escape = true 62 | else 63 | data = data .. now 64 | end 65 | end 66 | end 67 | end 68 | local function sRoot() 69 | local p = s 70 | function s(x) 71 | if #x > 0 then 72 | s = p 73 | local tag, rem = string.sub(x, 1, 1), string.sub(x, 2) 74 | if tag == '!' then 75 | s(nil, rem) 76 | elseif tag == '#' then 77 | sNumber() 78 | s(rem) 79 | elseif tag == '@' then 80 | sString() 81 | s(rem) 82 | elseif tag == '+' then 83 | s(true, rem) 84 | elseif tag == '-' then 85 | s(false, rem) 86 | elseif tag == '=' then 87 | sTable() 88 | s(rem) 89 | else 90 | error('invalid tag: ' .. tag) 91 | end 92 | end 93 | end 94 | end 95 | function sTable() 96 | local p, data, key = s, {} 97 | function s(x, rem) 98 | if key == nil then 99 | if x == nil then 100 | s = p 101 | s(data, rem) 102 | else 103 | key = x 104 | sRoot() 105 | s(rem) 106 | end 107 | else 108 | data[key] = x 109 | key = nil 110 | sRoot() 111 | s(rem) 112 | end 113 | end 114 | sRoot() 115 | end 116 | function s(x, rem) 117 | cb(x) 118 | sRoot() 119 | s(rem) 120 | end 121 | sRoot() 122 | return function(x) 123 | s(x) 124 | end 125 | end 126 | 127 | local function resolve(short) 128 | for addr in component.list(short) do 129 | return addr 130 | end 131 | for addr in component.list() do 132 | if string.lower(string.sub(addr, 1, #short)) == string.lower(short) then 133 | return addr 134 | end 135 | end 136 | end 137 | 138 | local gpu = resolve("gpu") 139 | if gpu then 140 | gpu = component.proxy(gpu) 141 | gpu.bind(resolve("screen")) 142 | gpu.setResolution(resX, resY) 143 | gpu.setDepth(gpu.maxDepth()) 144 | gpu.setBackground(0) 145 | end 146 | local function print(p) 147 | if gpu then 148 | gpu.copy(1, 1, resX, resY, 0, -1) 149 | gpu.fill(1, resY, resX, resY, ' ') 150 | gpu.setForeground(p.color) 151 | gpu.set(1, resY, p.text) 152 | end 153 | if p.beep then 154 | computer.beep(p.beep) 155 | end 156 | end 157 | 158 | local dbAddr, db = resolve("database") 159 | if dbAddr then 160 | db = component.proxy(dbAddr) 161 | end 162 | 163 | local invCache = {} 164 | local function getInv(invName) 165 | if not invCache[invName] then 166 | invCache[invName] = component.proxy(resolve(invName)) 167 | end 168 | return invCache[invName] 169 | end 170 | 171 | local inet = component.proxy(resolve("internet")) 172 | while true do 173 | print{text = "Connecting to " .. clientName .. "@" .. serverAddr .. ":" .. serverPort, color = 0xFFFF00} 174 | local socket = inet.connect(serverAddr, serverPort) 175 | local timeout = computer.uptime() + 3 176 | while socket and not socket.finishConnect() do 177 | if computer.uptime() > timeout then 178 | socket.close() 179 | socket = nil 180 | break 181 | end 182 | end 183 | if socket then 184 | local writeBuffer = encode(clientName) 185 | print{text = "Connected", color = 0x00FF00, beep = 440} 186 | local onRead = decode(function(p) 187 | for _, p in ipairs(p) do 188 | local inv, result 189 | if p.inv then inv = getInv(p.inv) end 190 | if p.op == "print" then 191 | print(p) 192 | elseif p.op == "list" then 193 | local stacks = inv.getAllStacks(p.side) 194 | if stacks == nil then 195 | result = {''} 196 | else 197 | local count = stacks.count() 198 | result = {} 199 | for slot = 1, count do 200 | local item = stacks() 201 | if item and item.name and item.size > 0 then 202 | item.aspects = nil 203 | result[slot] = item 204 | elseif slot == count then 205 | result[slot] = '' 206 | end 207 | end 208 | end 209 | elseif p.op == "listME" then 210 | result = {} 211 | for _, item in ipairs(inv.getItemsInNetwork()) do 212 | if item and item.name and item.size > 0 then 213 | item.aspects = nil 214 | table.insert(result, item) 215 | end 216 | end 217 | elseif p.op == "xferME" then 218 | local me = getInv(p.me) 219 | db.clear(1) 220 | me.store(p.filter, dbAddr, p.entry, 1) 221 | me.setInterfaceConfiguration(p.entry, dbAddr, p.entry, p.size) 222 | inv.transferItem(table.unpack(p.args)) 223 | me.setInterfaceConfiguration(1) 224 | elseif p.op == "call" then 225 | -- transferItem (OC): fromSide, toSide, [size, [fromSlot, [toSlot]]] 226 | result = {inv[p.fn](table.unpack(p.args))} 227 | else 228 | error("invalid op") 229 | end 230 | writeBuffer = writeBuffer .. encode(result) 231 | end 232 | end) 233 | while true do 234 | if #writeBuffer > 0 then 235 | local n = socket.write(writeBuffer) 236 | if not n then 237 | print{text = "Connection closed (write)", color = 0xFF0000, beep = 880} 238 | socket.close() 239 | break 240 | elseif n > 0 then 241 | writeBuffer = string.sub(writeBuffer, n + 1) 242 | end 243 | end 244 | local data = socket.read() 245 | if data then 246 | local ok, err = pcall(onRead, data) 247 | if not ok then 248 | print{text = err, color = 0xFF0000, beep = 880} 249 | socket.close() 250 | break 251 | end 252 | else 253 | print{text = "Connection closed (read)", color = 0xFF0000, beep = 880} 254 | socket.close() 255 | break 256 | end 257 | end 258 | else 259 | print{text = "Failed to connect", color = 0xFF0000, beep = 880} 260 | end 261 | end 262 | -------------------------------------------------------------------------------- /client/loader.lua: -------------------------------------------------------------------------------- 1 | load((function() 2 | local content = "" 3 | for chunk in component.invoke(component.list("internet")(), "request", "https://leu-235.com/oc-scripts/client.lua").read do 4 | content = content .. chunk 5 | end 6 | return content 7 | end)())("leu-235.com", 1847, "clientName", 80, 25) 8 | -------------------------------------------------------------------------------- /client/serialize.lua: -------------------------------------------------------------------------------- 1 | local encode 2 | (function() 3 | local encMap = { 4 | ['nil'] = function(x) 5 | return '!' 6 | end, 7 | number = function(x) 8 | return '#' .. x .. '@' 9 | end, 10 | string = function(x) 11 | return '@' .. string.gsub(x, '@', '@.') .. '@~' 12 | end, 13 | boolean = function(x) 14 | if x then return '+' else return '-' end 15 | end, 16 | table = function(x) 17 | local result = '=' 18 | for k, v in pairs(x) do 19 | result = result .. encode(k) .. encode(v) 20 | end 21 | return result .. '!' 22 | end 23 | } 24 | function encode(x) 25 | return encMap[type(x)](x) 26 | end 27 | end)() 28 | 29 | local function decode(cb) 30 | local s, sTable 31 | local function sNumber() 32 | local p, data = s, '' 33 | function s(x) 34 | local pos = string.find(x, '@') 35 | if pos then 36 | s = p 37 | s(tonumber(data .. string.sub(x, 1, pos - 1)), string.sub(x, pos + 1)) 38 | else 39 | data = data .. x 40 | end 41 | end 42 | end 43 | local function sString() 44 | local p, data, escape = s, '', false 45 | function s(x) 46 | for i = 1, #x do 47 | local now = string.sub(x, i, i) 48 | if escape then 49 | if now == '.' then 50 | data = data .. '@' 51 | escape = false 52 | elseif now == '~' then 53 | s = p 54 | return s(data, string.sub(x, i + 1)) 55 | else 56 | error('unknown escape: ' .. now) 57 | end 58 | elseif now == '@' then 59 | escape = true 60 | else 61 | data = data .. now 62 | end 63 | end 64 | end 65 | end 66 | local function sRoot() 67 | local p = s 68 | function s(x) 69 | if #x > 0 then 70 | s = p 71 | local tag, rem = string.sub(x, 1, 1), string.sub(x, 2) 72 | if tag == '!' then 73 | s(nil, rem) 74 | elseif tag == '#' then 75 | sNumber() 76 | s(rem) 77 | elseif tag == '@' then 78 | sString() 79 | s(rem) 80 | elseif tag == '+' then 81 | s(true, rem) 82 | elseif tag == '-' then 83 | s(false, rem) 84 | elseif tag == '=' then 85 | sTable() 86 | s(rem) 87 | else 88 | error('invalid tag: ' .. tag) 89 | end 90 | end 91 | end 92 | end 93 | function sTable() 94 | local p, data, key = s, {} 95 | function s(x, rem) 96 | if key == nil then 97 | if x == nil then 98 | s = p 99 | s(data, rem) 100 | else 101 | key = x 102 | sRoot() 103 | s(rem) 104 | end 105 | else 106 | data[key] = x 107 | key = nil 108 | sRoot() 109 | s(rem) 110 | end 111 | end 112 | sRoot() 113 | end 114 | function s(x, rem) 115 | cb(x) 116 | sRoot() 117 | s(rem) 118 | end 119 | sRoot() 120 | return function(x) 121 | s(x) 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /client/upload.sh: -------------------------------------------------------------------------------- 1 | scp -i ../../lckey/repo client.lua ec2-user@leu-235.com:/home/ec2-user/client.lua 2 | ssh -t -i ../../lckey/repo ec2-user@leu-235.com "sudo mv /home/ec2-user/client.lua /usr/share/nginx/leu-235/oc-scripts/client.lua" 3 | -------------------------------------------------------------------------------- /comp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb0124/OCRemote/38218cf5367a7f8e9cd8799cff14649a2712fdec/comp.gif -------------------------------------------------------------------------------- /recipe-help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb0124/OCRemote/38218cf5367a7f8e9cd8799cff14649a2712fdec/recipe-help.png -------------------------------------------------------------------------------- /server/CPPImpl/.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | build 3 | -------------------------------------------------------------------------------- /server/CPPImpl/Actions.cpp: -------------------------------------------------------------------------------- 1 | #include "Actions.h" 2 | 3 | namespace Actions { 4 | void Print::dump(STable &s) { 5 | s["op"] = "print"; 6 | s["color"] = static_cast(color); 7 | s["text"] = std::move(text); 8 | if (beep > 0) s["beep"] = beep; 9 | } 10 | 11 | void List::dump(STable &s) { 12 | s["op"] = "list"; 13 | s["side"] = static_cast(side); 14 | s["inv"] = std::move(inv); 15 | } 16 | 17 | void List::onResult(SValue s) { 18 | auto sItems(sTableToArray(std::move(std::get(s)))); 19 | std::vector items; 20 | items.reserve(sItems.size()); 21 | for (size_t i{}; i < sItems.size(); ++i) { 22 | if (!std::holds_alternative(sItems[i])) 23 | items.emplace_back(); 24 | else 25 | items.emplace_back(parseItemStack(std::move(sItems[i]))); 26 | } 27 | Promise>::onResult(std::move(items)); 28 | } 29 | 30 | void ListME::dump(STable &s) { 31 | s["op"] = "listME"; 32 | s["inv"] = std::move(inv); 33 | } 34 | 35 | void ListME::onResult(SValue s) { 36 | auto sItems(sTableToArray(std::move(std::get(s)))); 37 | std::vector items; 38 | items.reserve(sItems.size()); 39 | for (size_t i{}; i < sItems.size(); ++i) 40 | items.emplace_back(parseItemStack(std::move(sItems[i]))); 41 | Promise>::onResult(std::move(items)); 42 | } 43 | 44 | void XferME::dump(STable &t) { 45 | t["op"] = "xferME"; 46 | t["size"] = static_cast(size); 47 | t["entry"] = static_cast(entry); 48 | t["filter"] = std::move(filter); 49 | t["args"] = arrayToSTable(std::move(args)); 50 | t["inv"] = std::move(inv); 51 | t["me"] = std::move(me); 52 | } 53 | 54 | void Call::dump(STable &s) { 55 | s["op"] = "call"; 56 | s["fn"] = std::move(fn); 57 | s["args"] = arrayToSTable(std::move(args)); 58 | s["inv"] = std::move(inv); 59 | } 60 | 61 | void Call::onResult(SValue s) { 62 | Promise::onResult(std::move(s)); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /server/CPPImpl/Actions.h: -------------------------------------------------------------------------------- 1 | #ifndef _ACTIONS_H_ 2 | #define _ACTIONS_H_ 3 | #include 4 | #include "Overload.h" 5 | #include "Item.h" 6 | 7 | template 8 | struct Listener { 9 | using Result = T; 10 | virtual ~Listener() = default; 11 | virtual void onFail(std::string cause) = 0; 12 | virtual void onResult(T result) = 0; 13 | }; 14 | 15 | template 16 | struct DummyListener : Listener { 17 | void onFail(std::string cause) override {} 18 | void onResult(T result) override {} 19 | }; 20 | 21 | template 22 | using SharedListener = std::shared_ptr>; 23 | 24 | template 25 | class Promise : public Listener { 26 | SharedListener next; 27 | public: 28 | void onFail(std::string cause) override { 29 | if (!next) throw std::logic_error("broken chain"); 30 | next->onFail(std::move(cause)); 31 | } 32 | 33 | void onResult(T result) override { 34 | if (!next) throw std::logic_error("broken chain"); 35 | next->onResult(std::move(result)); 36 | } 37 | 38 | void listen(SharedListener x) { 39 | if (next) throw std::logic_error("already listened"); 40 | next = std::move(x); 41 | } 42 | 43 | template 44 | auto map(std::weak_ptr alive, Fn fn) { 45 | using To = std::invoke_result_t; 46 | auto result(std::make_shared>()); 47 | 48 | struct Impl : Listener { 49 | SharedListener to; 50 | std::weak_ptr alive; 51 | Fn fn; 52 | 53 | Impl(SharedListener to, std::weak_ptr alive, Fn fn) 54 | :to(std::move(to)), alive(std::move(alive)), fn(std::move(fn)) {} 55 | 56 | void onFail(std::string cause) override { 57 | to->onFail(std::move(cause)); 58 | } 59 | 60 | void onResult(T result) override { 61 | if (alive.expired()) 62 | to->onFail("node died"); 63 | else 64 | to->onResult(fn(std::move(result))); 65 | } 66 | }; 67 | 68 | listen(std::make_shared(result, std::move(alive), std::move(fn))); 69 | return result; 70 | } 71 | 72 | template 73 | auto mapTo(To value) { 74 | auto result(std::make_shared>()); 75 | 76 | struct Impl : Listener { 77 | SharedListener to; 78 | To value; 79 | 80 | Impl(SharedListener to, To value) 81 | :to(std::move(to)), value(std::move(value)) {} 82 | 83 | void onFail(std::string cause) override { 84 | to->onFail(std::move(cause)); 85 | } 86 | 87 | void onResult(T) override { 88 | to->onResult(std::move(value)); 89 | } 90 | }; 91 | 92 | listen(std::make_shared(result, std::move(value))); 93 | return result; 94 | } 95 | 96 | template 97 | auto then(std::weak_ptr alive, Fn fn) { 98 | using To = typename std::invoke_result_t::element_type::Result; 99 | auto result(std::make_shared>()); 100 | 101 | struct Impl : Listener { 102 | SharedListener to; 103 | std::weak_ptr alive; 104 | Fn fn; 105 | 106 | Impl(SharedListener to, std::weak_ptr alive, Fn fn) 107 | :to(std::move(to)), alive(std::move(alive)), fn(std::move(fn)) {} 108 | 109 | void onFail(std::string cause) override { 110 | to->onFail(std::move(cause)); 111 | } 112 | 113 | void onResult(T result) override { 114 | if (alive.expired()) 115 | to->onFail("node died"); 116 | else 117 | fn(std::move(result))->listen(std::move(to)); 118 | } 119 | }; 120 | 121 | listen(std::make_shared(result, std::move(alive), std::move(fn))); 122 | return result; 123 | } 124 | 125 | template 126 | std::shared_ptr> finally(std::weak_ptr alive, Fn fn) { 127 | auto result(std::make_shared>()); 128 | 129 | struct Impl : Listener { 130 | SharedListener to; 131 | std::weak_ptr alive; 132 | Fn fn; 133 | 134 | Impl(SharedListener to, std::weak_ptr alive, Fn fn) 135 | :to(std::move(to)), alive(std::move(alive)), fn(std::move(fn)) {} 136 | 137 | void onFail(std::string cause) override { 138 | if (!alive.expired()) 139 | fn(); 140 | to->onFail(std::move(cause)); 141 | } 142 | 143 | void onResult(T result) override { 144 | if (!alive.expired()) 145 | fn(); 146 | to->onResult(std::move(result)); 147 | } 148 | }; 149 | 150 | listen(std::make_shared(result, std::move(alive), std::move(fn))); 151 | return result; 152 | } 153 | 154 | static std::shared_ptr>> all(const std::vector>> &xs) { 155 | if (xs.empty()) throw std::runtime_error("waiting for nothing"); 156 | 157 | struct Context { 158 | std::shared_ptr>> to{std::make_shared>>()}; 159 | std::variant, std::string> result; 160 | size_t nRemaining; 161 | 162 | explicit Context(size_t nTotal) 163 | :result(std::in_place_type>, nTotal), nRemaining(nTotal) {} 164 | 165 | void decreaseRemaining() { 166 | if (!--nRemaining) { 167 | std::visit(Overload{ 168 | [this](std::vector &xs) { 169 | to->onResult(std::move(xs)); 170 | }, 171 | [this](std::string &x) { 172 | to->onFail(std::move(x)); 173 | } 174 | }, result); 175 | } 176 | } 177 | 178 | void onResult(size_t which, T &x) { 179 | std::visit(Overload{ 180 | [&](std::vector &xs) { 181 | xs[which] = std::move(x); 182 | }, 183 | [](std::string &) {} 184 | }, result); 185 | decreaseRemaining(); 186 | } 187 | 188 | void onFail(std::string &reason) { 189 | std::visit(Overload{ 190 | [&](std::vector &xs) { 191 | result = std::move(reason); 192 | }, 193 | [&](std::string &x) { 194 | x += "; " + reason; 195 | } 196 | }, result); 197 | decreaseRemaining(); 198 | } 199 | }; 200 | 201 | struct Impl : Listener { 202 | std::shared_ptr context; 203 | size_t index; 204 | 205 | Impl(const std::shared_ptr &context, size_t index) 206 | :context(context), index(index) {} 207 | 208 | void onFail(std::string cause) override { 209 | context->onFail(cause); 210 | } 211 | 212 | void onResult(T result) override { 213 | context->onResult(index, result); 214 | } 215 | }; 216 | 217 | auto context(std::make_shared(xs.size())); 218 | for (size_t i = 0; i < xs.size(); ++i) { 219 | xs[i]->listen(std::make_shared(context, i)); 220 | } 221 | return context->to; 222 | } 223 | }; 224 | template 225 | using SharedPromise = std::shared_ptr>; 226 | 227 | template 228 | inline SharedPromise scheduleTrivialPromise(F &dispatcher) { 229 | auto result(std::make_shared>()); 230 | dispatcher([result]() { result->onResult({}); }); 231 | return result; 232 | } 233 | 234 | template 235 | inline SharedPromise scheduleFailingPromise(F &dispatcher, std::string reason) { 236 | auto result(std::make_shared>()); 237 | dispatcher([result, reason(std::move(reason))]() { result->onFail(std::move(reason)); }); 238 | return result; 239 | } 240 | 241 | namespace Actions { 242 | enum { 243 | bottom = 0, down = 0, yn = 0, 244 | top = 1, up = 1, yp = 1, 245 | back = 2, north = 2, zn = 2, 246 | front = 3, south = 3, zp = 3, 247 | right = 4, west = 4, xn = 4, 248 | left = 5, east = 5, xp = 5 249 | }; 250 | 251 | struct Base : Listener { 252 | virtual void dump(STable&) = 0; 253 | }; 254 | 255 | template 256 | struct Impl : Base, Promise { 257 | void onFail(std::string cause) override { 258 | Promise::onFail(std::move(cause)); 259 | } 260 | }; 261 | 262 | struct ImplUnit : Impl { 263 | void onResult(SValue) override { 264 | Promise::onResult(std::monostate()); 265 | } 266 | }; 267 | 268 | struct Print : ImplUnit { 269 | std::string text; 270 | uint32_t color{0xffffffu}; 271 | double beep{-1.}; 272 | void dump(STable&) override; 273 | }; 274 | 275 | struct List : Impl> { 276 | std::string inv; 277 | int side; 278 | void dump(STable&) override; 279 | void onResult(SValue) override; 280 | }; 281 | 282 | struct ListME : Impl> { 283 | std::string inv; 284 | void dump(STable&) override; 285 | void onResult(SValue) override; 286 | }; 287 | 288 | struct XferME : ImplUnit { 289 | std::string me, inv; 290 | SValue filter; 291 | std::vector args; 292 | int size, entry; 293 | void dump(STable&) override; 294 | }; 295 | 296 | struct Call : Impl { 297 | std::string inv, fn; 298 | std::vector args; 299 | void dump(STable&) override; 300 | void onResult(SValue) override; 301 | }; 302 | } 303 | 304 | using SharedAction = std::shared_ptr; 305 | 306 | #endif 307 | -------------------------------------------------------------------------------- /server/CPPImpl/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | set(CMAKE_CXX_STANDARD 17) 3 | project(OCRemote) 4 | FIND_PACKAGE(Boost REQUIRED) 5 | add_executable (OCRemote Actions.cpp Actions.h Entry.cpp Factory.cpp Factory.h 6 | Item.cpp Item.h Overload.h Processes.cpp Processes.h Serialize.cpp Serialize.h 7 | Server.cpp Server.h Storages.cpp Storages.h WeakCallback.h) 8 | target_include_directories(OCRemote PRIVATE ${Boost_INCLUDE_DIR}) 9 | 10 | IF (WIN32) 11 | add_definitions(-DBOOST_ALL_NO_LIB) 12 | add_definitions(-D_WIN32_WINNT=0x0601) 13 | add_definitions(-D_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS) 14 | target_link_libraries(OCRemote ws2_32 mswsock) 15 | ELSE () 16 | set(THREADS_PREFER_PTHREAD_FLAG ON) 17 | find_package(Threads) 18 | target_link_libraries(OCRemote Threads::Threads) 19 | ENDIF () 20 | -------------------------------------------------------------------------------- /server/CPPImpl/Factory.h: -------------------------------------------------------------------------------- 1 | #ifndef _FACTORY_H_ 2 | #define _FACTORY_H_ 3 | #include 4 | #include 5 | #include "Server.h" 6 | #include "Actions.h" 7 | 8 | struct Factory; 9 | 10 | struct AccessAddr : Access { 11 | std::string addr; 12 | AccessAddr(std::string client, std::string addr) 13 | :Access(std::move(client)), addr(std::move(addr)) {} 14 | }; 15 | 16 | struct AccessBus : AccessAddr { 17 | int sideBus; 18 | AccessBus(std::string client, std::string addr, int sideBus) 19 | :AccessAddr(std::move(client), std::move(addr)), sideBus(sideBus) {} 20 | }; 21 | 22 | struct AccessInv : AccessBus { 23 | int sideInv; 24 | AccessInv(std::string client, std::string addr, int sideInv, int sideBus) 25 | :AccessBus(std::move(client), std::move(addr), sideBus), sideInv(sideInv) {} 26 | }; 27 | 28 | struct Process { 29 | Factory &factory; 30 | std::string name; 31 | Process(Factory &factory, std::string name) 32 | :factory(factory), name(std::move(name)) {} 33 | virtual ~Process() = default; 34 | virtual SharedPromise cycle() = 0; 35 | }; 36 | using UniqueProcess = std::unique_ptr; 37 | 38 | struct Provider { 39 | Factory &factory; 40 | int avail; 41 | const int priority; 42 | Provider(Factory &factory, int avail, int priority) :factory(factory), avail(avail), priority(priority) {} 43 | virtual ~Provider() = default; 44 | virtual SharedPromise extract(int size, size_t slot) = 0; 45 | }; 46 | using SharedProvider = std::shared_ptr; 47 | struct SharedProviderLess { bool operator()(const SharedProvider &x, const SharedProvider &y) const { return x->priority < y->priority; } }; 48 | 49 | struct Storage { 50 | Factory &factory; 51 | Storage(Factory &factory) :factory(factory) {} 52 | virtual ~Storage() = default; 53 | virtual void endOfCycle() {}; 54 | virtual SharedPromise update() = 0; 55 | virtual std::optional sinkPriority(const Item&) = 0; 56 | virtual std::pair> sink(const ItemStack&, size_t slot) = 0; 57 | }; 58 | using UniqueStorage = std::unique_ptr; 59 | 60 | struct Reservation { 61 | std::vector> providers; 62 | SharedPromise extract(size_t slot) const; 63 | }; 64 | 65 | class ItemInfo { 66 | std::priority_queue, SharedProviderLess> providers; 67 | int nAvail{}, nBackup{}; 68 | public: 69 | void addProvider(SharedProvider provider); 70 | void backup(int size); 71 | int getAvail(bool allowBackup) const; 72 | Reservation reserve(int size); 73 | }; 74 | 75 | template 76 | struct RecipeIn { 77 | SharedItemFilter item; 78 | int size{1}, extraBackup{}; 79 | bool allowBackup{}; 80 | T data; 81 | 82 | RecipeIn(SharedItemFilter item, int size, T data, bool allowBackup = false, int extraBackup = 0) 83 | :item(std::move(item)), size(size), extraBackup(extraBackup), allowBackup(allowBackup), data(std::move(data)) {} 84 | RecipeIn(SharedItemFilter item, int size) :item(std::move(item)), size(size) {} 85 | RecipeIn(SharedItemFilter item) :item(std::move(item)) {} 86 | }; 87 | 88 | struct RecipeOut { 89 | SharedItemFilter item; 90 | int size; 91 | }; 92 | 93 | template 94 | struct Recipe { 95 | std::vector out; 96 | std::vector> in; 97 | T data; 98 | }; 99 | 100 | template 101 | struct Demand { 102 | const Recipe *recipe; 103 | std::vector in; 104 | int inAvail; 105 | float fullness; 106 | Demand(const Recipe *recipe) :recipe(recipe) {} 107 | }; 108 | 109 | struct InputAvailInfo { 110 | int avail, needed; 111 | bool allowBackup; 112 | InputAvailInfo() :needed() {} 113 | }; 114 | 115 | struct Factory { 116 | Server &s; 117 | const std::shared_ptr alive{std::make_shared()}; 118 | private: 119 | int minCycleTime; 120 | std::vector logClients; 121 | std::vector> backups; 122 | std::vector processes; 123 | 124 | size_t nCycles{}; 125 | std::chrono::steady_clock::time_point cycleStartTime{std::chrono::steady_clock::now()}; 126 | std::shared_ptr cycleDelayTimer; 127 | void endOfCycle(); 128 | 129 | std::vector storages; 130 | std::unordered_map items; 131 | std::unordered_multimap nameMap, labelMap; 132 | SharedPromise updateAndBackupItems(); 133 | void insertItem(std::vector> &promises, size_t slot, ItemStack stack); 134 | 135 | std::vector busAccesses; 136 | std::unordered_set busAllocations; 137 | std::list> busWaitQueue; 138 | std::vector busFreeQueue; 139 | bool isBusRunning{}, endOfCycleAfterBusUpdate{}; 140 | size_t nBusUpdates{}; 141 | void doBusUpdate(); 142 | public: 143 | void log(std::string msg, uint32_t color = 0xffffffu, double beep = -1.f); 144 | ItemInfo &getOrAddItemInfo(const SharedItem &item); 145 | SharedItem getItem(const ItemFilters::Base &filter); 146 | int getAvail(const SharedItem &item, bool allowBackup); 147 | Reservation reserve(const std::string &reason, const SharedItem &item, int size); 148 | template 149 | void resolveRecipeInputs(const Recipe &recipe, Demand &demand, bool clipToMaxStackSize) { 150 | demand.inAvail = std::numeric_limits::max(); 151 | std::unordered_map avails; 152 | for (auto &in : recipe.in) { 153 | auto &inItem(demand.in.emplace_back(getItem(*in.item))); 154 | if (!inItem) { 155 | demand.inAvail = 0; 156 | continue; 157 | } 158 | auto itr(avails.try_emplace(inItem)); 159 | auto &info(itr.first->second); 160 | if (itr.second || info.allowBackup && !in.allowBackup) { 161 | info.avail = std::max(0, getAvail(inItem, in.allowBackup) - in.extraBackup); 162 | info.allowBackup = in.allowBackup; 163 | } 164 | info.needed += in.size; 165 | if (clipToMaxStackSize) { 166 | demand.inAvail = std::min(demand.inAvail, inItem->maxSize / in.size); 167 | } 168 | } 169 | for (auto &entry : avails) { 170 | demand.inAvail = std::min(demand.inAvail, entry.second.avail / entry.second.needed); 171 | } 172 | } 173 | template 174 | std::vector> getDemand(const std::vector> &recipes, bool clipToMaxStackSize = true) { 175 | std::vector> result; 176 | for (auto &recipe : recipes) { 177 | auto &demand(result.emplace_back(&recipe)); 178 | demand.fullness = 2.f; 179 | if (!recipe.out.empty()) { 180 | bool full{true}; 181 | for (auto &i : recipe.out) { 182 | int outAvail = getAvail(getItem(*i.item), true); 183 | if (outAvail >= i.size) 184 | continue; 185 | full = false; 186 | float fullness{static_cast(outAvail) / i.size}; 187 | if (fullness < demand.fullness) 188 | demand.fullness = fullness; 189 | } 190 | if (full) { 191 | result.pop_back(); 192 | continue; 193 | } 194 | } 195 | resolveRecipeInputs(recipe, demand, clipToMaxStackSize); 196 | if (!demand.inAvail) { 197 | result.pop_back(); 198 | continue; 199 | } 200 | } 201 | std::sort(result.begin(), result.end(), [](const Demand &x, const Demand &y) { 202 | return x.fullness < y.fullness; 203 | }); 204 | return result; 205 | } 206 | SharedPromise busAllocate(); 207 | void busFree(size_t slot, bool hasItem); 208 | void busFree(const std::vector &slots, bool hasItem); 209 | 210 | Factory(Server &s, int minCycleTime, decltype(logClients) logClients, decltype(busAccesses) busAccesses) 211 | :s(s), minCycleTime(minCycleTime), logClients(std::move(logClients)), busAccesses(std::move(busAccesses)) {} 212 | void addStorage(UniqueStorage storage) { storages.emplace_back(std::move(storage)); } 213 | void addBackup(SharedItemFilter filter, int size) { backups.emplace_back(std::move(filter), size); } 214 | void addProcess(UniqueProcess process) { processes.emplace_back(std::move(process)); } 215 | void start(); 216 | }; 217 | 218 | #endif 219 | -------------------------------------------------------------------------------- /server/CPPImpl/Item.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Item.h" 3 | 4 | SharedItem placeholderItem(std::make_shared(Item{ 5 | "OCRemote.placeholderItem.name", 6 | "OCRemote.placeholderItem.label", 7 | 0, 0, 1, false, {} 8 | })); 9 | 10 | void Item::serialize(SValue &s) const { 11 | auto &t(std::get(s = others)); 12 | t["name"] = name; 13 | t["label"] = label; 14 | t["damage"] = static_cast(damage); 15 | t["maxDamage"] = static_cast(maxDamage); 16 | t["maxSize"] = static_cast(maxSize); 17 | t["hasTag"] = hasTag; 18 | } 19 | 20 | bool operator==(const Item &x, const Item &y) { 21 | if (&x == &y) 22 | return true; 23 | return x.name == y.name 24 | && x.label == y.label 25 | && x.damage == y.damage 26 | && x.maxDamage == y.maxDamage 27 | && x.maxSize == y.maxSize 28 | && x.hasTag == y.hasTag 29 | && x.others == y.others; 30 | } 31 | 32 | size_t hash_value(const Item &x) { 33 | size_t result{}; 34 | boost::hash_combine(result, x.name); 35 | boost::hash_combine(result, x.label); 36 | boost::hash_combine(result, x.damage); 37 | boost::hash_combine(result, x.maxDamage); 38 | boost::hash_combine(result, x.maxSize); 39 | boost::hash_combine(result, x.hasTag); 40 | boost::hash_combine(result, serialize(x.others)); 41 | return result; 42 | } 43 | 44 | namespace { 45 | template struct TransferHelper { void operator()(SValue &from, T &to) const { to = std::move(std::get(from)); } }; 46 | template<> struct TransferHelper { void operator()(SValue &from, int &to) const { to = static_cast(std::get(from)); } }; 47 | template void transferHelper(SValue &from, T &to) { TransferHelper()(from, to); } 48 | } 49 | 50 | SharedItemStack parseItemStack(SValue &&s) { 51 | auto result(std::make_shared()); 52 | result->item = std::make_shared(); 53 | auto &t(std::get(s)); 54 | auto transfer([&t](const std::string &key, auto &to) { 55 | auto itr(t.find(key)); 56 | if (itr == t.end()) 57 | throw std::runtime_error("key \"" + key + "\" not found"); 58 | transferHelper(itr->second, to); 59 | t.erase(itr); 60 | }); 61 | struct Int { int &to; void operator()(SValue &x) const { to = static_cast(std::get(x)); } }; 62 | transfer("size", result->size); 63 | transfer("name", result->item->name); 64 | transfer("label", result->item->label); 65 | transfer("damage", result->item->damage); 66 | transfer("maxDamage", result->item->maxDamage); 67 | transfer("maxSize", result->item->maxSize); 68 | transfer("hasTag", result->item->hasTag); 69 | result->item->others = std::move(t); 70 | return result; 71 | } 72 | 73 | std::vector cloneInventory(const std::vector &inventory) { 74 | std::vector result; 75 | result.reserve(inventory.size()); 76 | for (auto &i : inventory) { 77 | if (i) 78 | result.emplace_back(std::make_shared(*i)); 79 | else 80 | result.emplace_back(); 81 | } 82 | return result; 83 | } 84 | 85 | InsertResult insertIntoInventory(std::vector &inventory, const SharedItem &item, int size) { 86 | InsertResult result; 87 | size = std::min(size, item->maxSize); 88 | std::optional firstEmptySlot; 89 | for (size_t slot{}; size && slot < inventory.size(); ++slot) { 90 | auto &stack(inventory[slot]); 91 | if (!stack) { 92 | if (!firstEmptySlot.has_value()) 93 | firstEmptySlot.emplace(slot); 94 | } else if (*stack->item == *item) { 95 | int toProc{std::min(size, item->maxSize - stack->size)}; 96 | if (toProc) { 97 | stack->size += toProc; 98 | result.totalSize += toProc; 99 | result.actions.emplace_back(slot, toProc); 100 | size -= toProc; 101 | } 102 | } 103 | } 104 | if (size && firstEmptySlot.has_value()) { 105 | auto &stack{*(inventory[*firstEmptySlot] = std::make_shared())}; 106 | stack.item = item; 107 | stack.size = size; 108 | result.totalSize += size; 109 | result.actions.emplace_back(*firstEmptySlot, size); 110 | } 111 | return result; 112 | } 113 | 114 | namespace ItemFilters { 115 | bool Name::filter(const Item &item) const { 116 | return item.name == name; 117 | } 118 | 119 | bool Label::filter(const Item &item) const { 120 | return item.label == label; 121 | } 122 | 123 | bool LabelName::filter(const Item &item) const { 124 | return item.label == label && item.name == name; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /server/CPPImpl/Item.h: -------------------------------------------------------------------------------- 1 | #ifndef _ITEM_H_ 2 | #define _ITEM_H_ 3 | #include "Serialize.h" 4 | 5 | struct Item { 6 | std::string name, label; 7 | int damage, maxDamage, maxSize; 8 | bool hasTag; 9 | STable others; 10 | void serialize(SValue &s) const; 11 | }; 12 | bool operator==(const Item &x, const Item &y); 13 | inline bool operator!=(const Item &x, const Item &y) { return !(x == y); } 14 | size_t hash_value(const Item&); 15 | using SharedItem = std::shared_ptr; 16 | struct SharedItemHash { size_t operator()(const SharedItem &x) const { return hash_value(*x); } }; 17 | struct SharedItemEqual { bool operator()(const SharedItem &x, const SharedItem &y) const { return *x == *y; } }; 18 | extern SharedItem placeholderItem; 19 | 20 | struct ItemStack { 21 | SharedItem item; 22 | int size; 23 | }; 24 | using SharedItemStack = std::shared_ptr; 25 | SharedItemStack parseItemStack(SValue&&); 26 | std::vector cloneInventory(const std::vector &inventory); 27 | 28 | struct InsertResult { 29 | int totalSize{}; 30 | std::vector> actions; 31 | }; 32 | InsertResult insertIntoInventory(std::vector &inventory, const SharedItem &item, int size); 33 | 34 | namespace ItemFilters { 35 | struct IndexVisitor { 36 | virtual ~IndexVisitor() = default; 37 | virtual void visit(const struct Name&) = 0; 38 | virtual void visit(const struct Label&) = 0; 39 | virtual void visit(const struct LabelName&) = 0; 40 | virtual void visit(const struct Base&) = 0; 41 | }; 42 | 43 | struct Base { 44 | virtual ~Base() = default; 45 | virtual bool filter(const Item&) const = 0; 46 | virtual void accept(IndexVisitor &v) const { v.visit(*this); } 47 | }; 48 | 49 | struct Name final : Base { 50 | const std::string name; 51 | explicit Name(std::string name) :name(std::move(name)) {} 52 | bool filter(const Item&) const override; 53 | void accept(IndexVisitor &v) const override { v.visit(*this); } 54 | }; 55 | 56 | struct Label final : Base { 57 | const std::string label; 58 | explicit Label(std::string label) :label(std::move(label)) {} 59 | bool filter(const Item&) const override; 60 | void accept(IndexVisitor &v) const override { v.visit(*this); } 61 | }; 62 | 63 | struct LabelName final : Base { 64 | const std::string label, name; 65 | explicit LabelName(std::string label, std::string name) 66 | :label(std::move(label)), name(std::move(name)) {} 67 | bool filter(const Item&) const override; 68 | void accept(IndexVisitor &v) const override { v.visit(*this); } 69 | }; 70 | } 71 | using SharedItemFilter = std::shared_ptr; 72 | 73 | #define DEF_FILTER_SHORTCUT(type)\ 74 | template\ 75 | inline std::shared_ptr filter##type(Ts &&...xs) {\ 76 | return std::make_shared(std::forward(xs)...);\ 77 | } 78 | DEF_FILTER_SHORTCUT(Name) 79 | DEF_FILTER_SHORTCUT(Label) 80 | DEF_FILTER_SHORTCUT(LabelName) 81 | 82 | template SharedItemFilter inline filterFn(F fn) { 83 | struct Impl : ItemFilters::Base { 84 | F fn; 85 | Impl(F fn) :fn(std::move(fn)) {} 86 | virtual bool filter(const Item &x) const { return fn(x); } 87 | }; 88 | return std::make_shared(std::move(fn)); 89 | } 90 | 91 | #endif 92 | -------------------------------------------------------------------------------- /server/CPPImpl/Overload.h: -------------------------------------------------------------------------------- 1 | #ifndef _OVERLOAD_H_ 2 | #define _OVERLOAD_H_ 3 | 4 | template struct Overload : T... { using T::operator()...; }; 5 | template Overload(T...) -> Overload; 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /server/CPPImpl/Processes.h: -------------------------------------------------------------------------------- 1 | #ifndef _PROCESSES_H_ 2 | #define _PROCESSES_H_ 3 | #include "Factory.h" 4 | 5 | template 6 | struct ProcessAccess : Process { 7 | std::vector accesses; 8 | ProcessAccess(Factory &factory, std::string name, decltype(accesses) accesses) 9 | :Process(factory, std::move(name)), accesses(std::move(accesses)) {} 10 | }; 11 | 12 | struct ProcessAccessInv : ProcessAccess { 13 | using ProcessAccess::ProcessAccess; 14 | SharedPromise processOutput(size_t slot, int size); 15 | }; 16 | 17 | using OutFilter = std::function; 18 | using SlotFilter = std::function; 19 | inline bool outAll(size_t slot, const ItemStack&) { return true; } 20 | 21 | struct ProcessSlotted : ProcessAccessInv { 22 | using Recipe = ::Recipe>; // eachSlotMaxInProc, slots 23 | std::vector inSlots; 24 | OutFilter outFilter; 25 | std::vector recipes; 26 | ProcessSlotted(Factory &factory, std::string name, decltype(accesses) accesses, 27 | decltype(inSlots) inSlots, OutFilter outFilter, decltype(recipes) recipes) 28 | :ProcessAccessInv(factory, std::move(name), std::move(accesses)), 29 | inSlots(std::move(inSlots)), outFilter(std::move(outFilter)), recipes(std::move(recipes)) {} 30 | SharedPromise cycle() override; 31 | }; 32 | 33 | struct NonConsumableInfo { 34 | size_t storageSlot; 35 | size_t craftingGridSlot; 36 | NonConsumableInfo(size_t storageSlot, size_t craftingGridSlot) 37 | :storageSlot(storageSlot), craftingGridSlot(craftingGridSlot) {} 38 | }; 39 | 40 | struct AccessCraftingRobot : Access { 41 | int sideBus; 42 | AccessCraftingRobot(std::string client, int sideBus) 43 | :Access(std::move(client)), sideBus(sideBus) {} 44 | }; 45 | 46 | struct ProcessCraftingRobot : ProcessAccess { 47 | // Note: can't craft more than one stack at a time. 48 | // Slots: 1, 2, 3 49 | // 4, 5, 6 50 | // 7, 8, 9 51 | // NonConsumableSlots: 52 | // 4, 8, 12, 13, 14, 15 53 | // with extra inventory: 17, 18, ... 54 | // (maxSets, nonConsumableInfo), slots 55 | using Recipe = ::Recipe>, std::vector>; 56 | std::vector recipes; 57 | size_t mapCraftingGridSlot(size_t slot); 58 | ProcessCraftingRobot(Factory &factory, std::string name, decltype(accesses) accesses, std::vector recipes) 59 | :ProcessAccess(factory, std::move(name), std::move(accesses)), recipes(std::move(recipes)) {} 60 | SharedPromise cycle() override; 61 | }; 62 | 63 | struct AccessRFToolsControlWorkbench : Access { 64 | std::string addrIn, addrOut; 65 | int sideBusIn, sideBusOut, sideNonConsumable; 66 | AccessRFToolsControlWorkbench(std::string client, std::string addrIn, std::string addrOut, 67 | int sideBusIn, int sideBusOut, int sideNonConsumable) 68 | :Access(std::move(client)), addrIn(std::move(addrIn)), addrOut(std::move(addrOut)), 69 | sideBusIn(sideBusIn), sideBusOut(sideBusOut), sideNonConsumable(sideNonConsumable) {} 70 | }; 71 | 72 | struct ProcessRFToolsControlWorkbench : ProcessAccess { 73 | using Recipe = ::Recipe>, std::vector>; 74 | std::vector recipes; 75 | ProcessRFToolsControlWorkbench(Factory &factory, std::string name, decltype(accesses) accesses, std::vector recipes) 76 | :ProcessAccess(factory, std::move(name), std::move(accesses)), recipes(std::move(recipes)) {} 77 | SharedPromise cycle() override; 78 | }; 79 | 80 | struct StockEntry { 81 | SharedItemFilter item; 82 | int toStock, extraBackup; 83 | bool allowBackup; 84 | StockEntry(SharedItemFilter item, int toStock, bool allowBackup = false, int extraBackup = 0) 85 | :item(std::move(item)), toStock(toStock), extraBackup(extraBackup), allowBackup(allowBackup) {} 86 | }; 87 | 88 | struct ProcessBuffered : ProcessAccessInv { 89 | using Recipe = ::Recipe; // maxInproc 90 | std::vector stockList; 91 | int recipeMaxInProc; 92 | SlotFilter slotFilter; 93 | OutFilter outFilter; 94 | std::vector recipes; 95 | ProcessBuffered(Factory &factory, std::string name, decltype(accesses) accesses, decltype(stockList) stockList, 96 | int recipeMaxInProc, SlotFilter slotFilter, OutFilter outFilter, decltype(recipes) recipes) 97 | :ProcessAccessInv(factory, std::move(name), std::move(accesses)), stockList(std::move(stockList)), 98 | recipeMaxInProc(recipeMaxInProc), slotFilter(std::move(slotFilter)), outFilter(std::move(outFilter)), recipes(std::move(recipes)) {} 99 | SharedPromise cycle() override; 100 | }; 101 | 102 | struct ProcessScatteringWorkingSet : ProcessAccessInv { 103 | // Note: single input only. 104 | using Recipe = ::Recipe<>; 105 | int eachSlotMaxInProc; 106 | std::vector inSlots; 107 | OutFilter outFilter; 108 | std::vector recipes; 109 | ProcessScatteringWorkingSet(Factory &factory, std::string name, decltype(accesses) accesses, 110 | int eachSlotMaxInProc, decltype(inSlots) inSlots, OutFilter outFilter, decltype(recipes) recipes) 111 | :ProcessAccessInv(factory, std::move(name), std::move(accesses)), eachSlotMaxInProc(eachSlotMaxInProc), 112 | inSlots(std::move(inSlots)), outFilter(std::move(outFilter)), recipes(std::move(recipes)) {} 113 | SharedPromise cycle() override; 114 | }; 115 | 116 | inline std::vector plantSowerInSlots() { return {6, 7, 8, 9, 10, 11, 12, 13, 14}; } 117 | 118 | struct InputlessEntry { 119 | SharedItemFilter item; 120 | int needed; 121 | }; 122 | 123 | struct ProcessInputless : ProcessAccessInv { 124 | SlotFilter slotFilter; 125 | std::vector entries; 126 | ProcessInputless(Factory &factory, std::string name, decltype(accesses) accesses, SlotFilter slotFilter, decltype(entries) entries) 127 | :ProcessAccessInv(factory, std::move(name), std::move(accesses)), slotFilter(std::move(slotFilter)), entries(std::move(entries)) {} 128 | SharedPromise cycle() override; 129 | }; 130 | 131 | struct ProcessReactor : ProcessAccess { 132 | int cyaniteNeeded; 133 | bool hasTurbine; 134 | // Typical address: br_reactor 135 | ProcessReactor(Factory &factory, std::string name, decltype(accesses) accesses, int cyaniteNeeded, bool hasTurbine) 136 | :ProcessAccess(factory, std::move(name), std::move(accesses)), cyaniteNeeded(cyaniteNeeded), hasTurbine(hasTurbine) {} 137 | SharedPromise getPV(); 138 | }; 139 | 140 | struct ProcessReactorHysteresis : ProcessReactor { 141 | double lowerBound, upperBound; 142 | int wasOn; 143 | ProcessReactorHysteresis(Factory &factory, std::string name, decltype(accesses) accesses, 144 | int cyaniteNeeded = 0, bool hasTurbine = false, double lowerBound = 0.3, double upperBound = 0.7) 145 | :ProcessReactor(factory, std::move(name), std::move(accesses), cyaniteNeeded, hasTurbine), 146 | lowerBound(lowerBound), upperBound(upperBound), wasOn(-1) {} 147 | SharedPromise cycle() override; 148 | }; 149 | 150 | struct ProcessReactorProportional : ProcessReactor { 151 | int prev; 152 | ProcessReactorProportional(Factory &factory, std::string name, decltype(accesses) accesses, int cyaniteNeeded = 0, bool hasTurbine = false) 153 | :ProcessReactor(factory, std::move(name), std::move(accesses), cyaniteNeeded, hasTurbine), prev(-1) {} 154 | SharedPromise cycle() override; 155 | }; 156 | 157 | struct ProcessReactorPID : ProcessReactor { 158 | double kP, kI, kD; 159 | bool isInit; 160 | std::chrono::time_point prevT; 161 | double prevE, accum; 162 | int prevOut; 163 | ProcessReactorPID(Factory &factory, std::string name, decltype(accesses) accesses, int cyaniteNeeded = 0, bool hasTurbine = false, 164 | double kP = 1, double kI = 0.01, double kD = 0, double initAccum = 0) 165 | :ProcessReactor(factory, std::move(name), std::move(accesses), cyaniteNeeded, hasTurbine), 166 | kP(kP), kI(kP * kI), kD(kP * kD), isInit(true), accum(initAccum), prevOut(-1) {} 167 | SharedPromise cycle() override; 168 | }; 169 | 170 | struct ProcessPlasticMixer : ProcessAccess { 171 | static const std::vector colorMap; 172 | int needed, prev; 173 | // Typical address: plastic_mixer 174 | ProcessPlasticMixer(Factory &factory, std::string name, decltype(accesses) accesses, int needed = 32) 175 | :ProcessAccess(factory, std::move(name), std::move(accesses)), needed(needed), prev(-1) {} 176 | SharedPromise cycle() override; 177 | }; 178 | 179 | struct AccessRedstone : AccessAddr { 180 | int side; 181 | AccessRedstone(std::string client, std::string addr, int side) 182 | :AccessAddr(std::move(client), std::move(addr)), side(side) {} 183 | }; 184 | 185 | struct ProcessRedstoneConditional : ProcessAccess { 186 | bool logSkip; 187 | std::function predicate; 188 | std::function precondition; 189 | std::unique_ptr child; 190 | ProcessRedstoneConditional(Factory &factory, std::string name, decltype(accesses) accesses, 191 | bool logSkip, decltype(precondition) precondition, decltype(predicate) predicate, decltype(child) child) 192 | :ProcessAccess(factory, std::move(name), std::move(accesses)), logSkip(logSkip), 193 | precondition(std::move(precondition)), predicate(std::move(predicate)), child(std::move(child)) {} 194 | SharedPromise cycle() override; 195 | }; 196 | 197 | struct ProcessRedstoneEmitter : ProcessAccess { 198 | int prevValue; 199 | std::function valueFn; 200 | ProcessRedstoneEmitter(Factory &factory, std::string name, decltype(accesses) accesses, decltype(valueFn) valueFn) 201 | :ProcessAccess(factory, std::move(name), std::move(accesses)), prevValue(-1), valueFn(std::move(valueFn)) {} 202 | SharedPromise cycle() override; 203 | static std::function makeNeeded(Factory &factory, std::string name, SharedItemFilter item, int toStock); 204 | }; 205 | 206 | struct FluxNetworkOutput { 207 | std::string name; 208 | std::vector accesses; 209 | std::function valueFn; 210 | }; 211 | 212 | struct ProcessFluxNetwork : ProcessAccess { 213 | std::vector> outputs; 214 | double lastEnergy; 215 | // Typical address: flux_controller 216 | ProcessFluxNetwork(Factory &factory, std::string name, decltype(accesses) accesses, std::vector outputs) 217 | :ProcessAccess(factory, std::move(name), std::move(accesses)) { 218 | for (auto &output : outputs) 219 | this->outputs.emplace_back(std::make_unique(factory, std::move(output.name), std::move(output.accesses), 220 | [this, valueFn(std::move(output.valueFn))]() { 221 | return valueFn(lastEnergy); 222 | })); 223 | } 224 | 225 | SharedPromise cycle() override; 226 | }; 227 | 228 | #endif 229 | -------------------------------------------------------------------------------- /server/CPPImpl/Serialize.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "Serialize.h" 4 | #include "Overload.h" 5 | 6 | STable arrayToSTable(std::vector &&array) { 7 | STable result; 8 | for (size_t i{}; i < array.size(); ++i) { 9 | if (!std::holds_alternative(array[i])) 10 | result[static_cast(i + 1)] = std::move(array[i]); 11 | } 12 | return result; 13 | } 14 | 15 | std::vector sTableToArray(STable&& table) { 16 | std::vector result; 17 | for (auto &entry : table) { 18 | auto key(std::get(entry.first) - 1); 19 | auto iKey(static_cast(key)); 20 | if (iKey != key) 21 | throw std::runtime_error("non-integer key"); 22 | if (result.size() <= iKey) 23 | result.resize(iKey + 1); 24 | result[iKey] = std::move(entry.second); 25 | } 26 | return result; 27 | } 28 | 29 | namespace { 30 | struct Serializer { 31 | std::string operator()(std::monostate) const { 32 | return "!"; 33 | } 34 | 35 | std::string operator()(double x) const { 36 | std::ostringstream os; 37 | os << "#" << std::setprecision(std::numeric_limits::max_digits10) << x << "@"; 38 | return os.str(); 39 | } 40 | 41 | std::string operator()(const std::string &x) const { 42 | std::string result{"@"}; 43 | for (char i : x) 44 | if (i == '@') 45 | result += "@."; 46 | else 47 | result.push_back(i); 48 | result += "@~"; 49 | return result; 50 | } 51 | 52 | std::string operator()(bool x) const { 53 | return x ? "+" : "-"; 54 | } 55 | 56 | std::string operator()(const STable &x) const { 57 | std::string result{"="}; 58 | for (auto &i : x) { 59 | result += std::visit(*this, static_cast(i.first)); 60 | result += serialize(i.second); 61 | } 62 | result.push_back('!'); 63 | return result; 64 | } 65 | }; 66 | } 67 | 68 | std::string serialize(const SValue &x) { 69 | return std::visit(Serializer{}, static_cast(x)); 70 | } 71 | 72 | std::string serialize(const STable &x) { 73 | return Serializer{}(x); 74 | } 75 | 76 | std::unique_ptr Deserializer::State::yield() { 77 | auto s(std::move(env.s)); 78 | env.s = std::move(p); 79 | return s; 80 | } 81 | 82 | void Deserializer::Number::shift(const char *data, size_t size) { 83 | while (size) { 84 | char now{*data}; 85 | ++data; --size; 86 | if ('@' == now) { 87 | auto s(yield()); 88 | return env.s->reduce(std::stod(buffer), data, size); 89 | } else { 90 | buffer.push_back(now); 91 | } 92 | } 93 | } 94 | 95 | void Deserializer::String::shift(const char *data, size_t size) { 96 | while (size) { 97 | char now{*data}; 98 | ++data; --size; 99 | if (escape) { 100 | if ('.' == now) { 101 | buffer.push_back('@'); 102 | escape = false; 103 | } else if ('~' == now) { 104 | auto s(yield()); 105 | return env.s->reduce(std::move(buffer), data, size); 106 | } else { 107 | throw std::runtime_error("unknown escape"); 108 | } 109 | } else if ('@' == now) { 110 | escape = true; 111 | } else { 112 | buffer.push_back(now); 113 | } 114 | } 115 | } 116 | 117 | void Deserializer::Root::shift(const char *data, size_t size) { 118 | if (!size) return; 119 | char now{*data}; 120 | ++data; --size; 121 | auto s(yield()); 122 | switch (now) { 123 | case '!': 124 | return env.s->reduce(std::monostate{}, data, size); 125 | case '#': 126 | env.enter(); 127 | return env.s->shift(data, size); 128 | case '@': 129 | env.enter(); 130 | return env.s->shift(data, size); 131 | case '+': 132 | return env.s->reduce(true, data, size); 133 | case '-': 134 | return env.s->reduce(false, data, size); 135 | case '=': 136 | env.enter(); 137 | return env.s->shift(data, size); 138 | default: 139 | throw std::runtime_error("invalid tag"); 140 | } 141 | } 142 | 143 | namespace { 144 | template struct Contains {}; 145 | template struct Contains : std::false_type {}; 146 | template struct Contains : 147 | std::bool_constant || Contains::value> {}; 148 | template struct VariantContains {}; 149 | template struct VariantContains> : Contains {}; 150 | } 151 | 152 | void Deserializer::Table::reduce(SValue x, const char *data, size_t size) { 153 | if (key.has_value()) { 154 | result.emplace(std::move(*key), std::move(x)); 155 | key.reset(); 156 | } else if (std::holds_alternative(x)) { 157 | auto s(yield()); 158 | return env.s->reduce(std::move(result), data, size); 159 | } else { 160 | std::visit([this](auto &x) { 161 | if constexpr (VariantContains, SKeyBase>::value) 162 | key.emplace(std::move(x)); 163 | else 164 | throw std::runtime_error("invalid table key"); 165 | }, static_cast(x)); 166 | } 167 | env.enter(); 168 | env.s->shift(data, size); 169 | } 170 | 171 | void Deserializer::Table::init() { 172 | env.enter(); 173 | } 174 | 175 | void Deserializer::Start::reduce(SValue x, const char *data, size_t size) { 176 | env.cb(std::move(x)); 177 | env.enter(); 178 | env.s->shift(data, size); 179 | } 180 | 181 | void Deserializer::Start::init() { 182 | env.enter(); 183 | } 184 | -------------------------------------------------------------------------------- /server/CPPImpl/Serialize.h: -------------------------------------------------------------------------------- 1 | #ifndef _SERIALIZE_H_ 2 | #define _SERIALIZE_H_ 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using SKeyBase = std::variant; 12 | struct SKey : SKeyBase { 13 | using SKeyBase::variant; 14 | SKey(const char *x) :SKeyBase(std::in_place_type, x) {} 15 | }; 16 | using STable = std::map; 17 | using SValueBase = std::variant; 18 | struct SValue : SValueBase { 19 | using SValueBase::variant; 20 | SValue(const char *x) :SValueBase(std::in_place_type, x) {} 21 | }; 22 | STable arrayToSTable(std::vector&&); 23 | std::vector sTableToArray(STable&&); 24 | std::string serialize(const SValue&); 25 | std::string serialize(const STable&); 26 | 27 | class Deserializer { 28 | struct State { 29 | Deserializer &env; 30 | std::unique_ptr p; 31 | State(Deserializer &env) :env(env), p(std::move(env.s)) {} 32 | virtual ~State() = default; 33 | virtual void reduce(SValue x, const char *data, size_t size) { throw std::logic_error("invalid reduce"); } 34 | virtual void shift(const char *data, size_t size) { throw std::logic_error("invalid shift"); } 35 | std::unique_ptr yield(); 36 | void init() {} 37 | }; 38 | 39 | class Number : public State { 40 | std::string buffer; 41 | public: 42 | using State::State; 43 | void shift(const char *data, size_t size) override; 44 | }; 45 | 46 | class String : public State { 47 | std::string buffer; 48 | bool escape{}; 49 | public: 50 | using State::State; 51 | void shift(const char *data, size_t size) override; 52 | }; 53 | 54 | struct Root : State { 55 | using State::State; 56 | void shift(const char *data, size_t size) override; 57 | }; 58 | 59 | class Table : public State { 60 | STable result; 61 | std::optional key; 62 | public: 63 | using State::State; 64 | void reduce(SValue x, const char *data, size_t size) override; 65 | void init(); 66 | }; 67 | 68 | struct Start : State { 69 | using State::State; 70 | void reduce(SValue x, const char *data, size_t size) override; 71 | void init(); 72 | }; 73 | 74 | std::function cb; 75 | std::unique_ptr s; 76 | template void enter() { 77 | s = std::make_unique(*this); 78 | static_cast(*s).init(); 79 | } 80 | public: 81 | Deserializer(decltype(cb) cb) :cb(std::move(cb)) { enter(); } 82 | void operator()(const char *data, size_t size) { s->shift(data, size); } 83 | }; 84 | 85 | #endif 86 | -------------------------------------------------------------------------------- /server/CPPImpl/Server.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Server.h" 3 | #include "WeakCallback.h" 4 | 5 | Server::Server(IOEnv &io, uint16_t port) 6 | :io(io), acceptor(io.io, boost::asio::ip::tcp::v6()) { 7 | acceptor.set_option(boost::asio::ip::tcp::socket::reuse_address(true)); 8 | acceptor.bind({boost::asio::ip::address_v6::any(), port}); 9 | acceptor.listen(); 10 | accept(); 11 | } 12 | 13 | void Server::accept() { 14 | auto socket(std::make_shared(io.io)); 15 | acceptor.async_accept(*socket, makeWeakCallback(alive, [this, socket]( 16 | const boost::system::error_code &error 17 | ) { 18 | if (error) 19 | throw boost::system::system_error(error, "async_accept failed"); 20 | accept(); 21 | clients.emplace_front(std::make_shared(*this, std::move(*socket)))->init(clients.begin()); 22 | })); 23 | } 24 | 25 | void Server::removeClient(Client &c) { 26 | if (c.getLogin()) 27 | logins.erase(*c.getLogin()); 28 | clients.erase(c.getItr()); 29 | } 30 | 31 | Client::Client(Server &s, boost::asio::ip::tcp::socket socket) 32 | :s(s), socket(std::move(socket)), d(std::bind(&Client::onPacket, this, std::placeholders::_1)) {} 33 | 34 | void Client::init(const Itr &x) { 35 | itr = x; 36 | const auto &host{socket.remote_endpoint()}; 37 | logHeader = host.address().to_string() + ":" + std::to_string(host.port()); 38 | log("connected"); 39 | read(); 40 | } 41 | 42 | Client::~Client() { 43 | log("disconnected"); 44 | std::string failureCause(logHeader + " disconnected"); 45 | 46 | if (!sendQueue.empty()) { 47 | s.io([sendQueue(std::move(sendQueue)), failureCause]() { 48 | for (auto &i : sendQueue) { 49 | for (auto &j : i) { 50 | j->onFail(failureCause); 51 | } 52 | } 53 | }); 54 | } 55 | 56 | if (!responseQueue.empty()) { 57 | s.io([responseQueue(std::move(responseQueue)), failureCause]() { 58 | for (auto &i : responseQueue) { 59 | i->onFail(failureCause); 60 | } 61 | }); 62 | } 63 | } 64 | 65 | void Client::log(const std::string &message) { 66 | std::cout << logHeader << ": " << message << std::endl; 67 | } 68 | 69 | void Client::read() { 70 | auto buffer(std::make_shared>()); 71 | socket.async_read_some(boost::asio::buffer(*buffer), makeWeakCallback(weak_from_this(), [this, buffer]( 72 | const boost::system::error_code &ec, size_t nRead 73 | ) { 74 | if (ec) { 75 | log("error reading length: " + ec.message()); 76 | s.removeClient(*this); 77 | } else { 78 | auto wk{weak_from_this()}; 79 | #if DUMP_PACKETS 80 | std::cout << logHeader << " >=> "; 81 | std::cout.write(buffer->data(), nRead); 82 | std::cout << std::endl; 83 | #endif 84 | try { 85 | d(buffer->data(), nRead); 86 | } catch (std::exception &e) { 87 | log(std::string("error decoding packet: ") + e.what()); 88 | s.removeClient(*this); 89 | } 90 | if (!wk.expired()) { 91 | read(); 92 | } 93 | } 94 | })); 95 | } 96 | 97 | void Client::onPacket(SValue p) { 98 | if (login) { 99 | if (responseQueue.empty()) { 100 | log("unexpected packet"); 101 | s.removeClient(*this); 102 | } else { 103 | responseQueue.front()->onResult(std::move(p)); 104 | responseQueue.pop_front(); 105 | updateTimer(true); 106 | } 107 | } else { 108 | login = std::get(p); 109 | logHeader += "(" + *login + ")"; 110 | log("logged in"); 111 | s.updateLogin(*this); 112 | } 113 | } 114 | 115 | void Server::updateLogin(Client &c) { 116 | auto result(logins.emplace(*c.getLogin(), &c)); 117 | if (!result.second) { 118 | Client *old = result.first->second; 119 | old->log("evicted"); 120 | clients.erase(old->getItr()); 121 | result.first->second = &c; 122 | } 123 | } 124 | 125 | void Server::enqueueActionGroup(const std::string &client, std::vector actions) { 126 | auto itr(logins.find(client)); 127 | if (itr == logins.end()) { 128 | io([actions(std::move(actions)), failureCause(client + " isn't connected")]() mutable { 129 | for (auto &i : actions) { 130 | i->onFail(std::move(failureCause)); 131 | } 132 | }); 133 | } else { 134 | itr->second->enqueueActionGroup(std::move(actions)); 135 | } 136 | } 137 | 138 | void Server::enqueueAction(const std::string &client, SharedAction action) { 139 | std::vector actions; 140 | actions.emplace_back(std::move(action)); 141 | enqueueActionGroup(client, std::move(actions)); 142 | } 143 | 144 | size_t Server::countPending(const std::string &client) const { 145 | auto itr(logins.find(client)); 146 | if (itr == logins.end()) 147 | return std::numeric_limits::max(); 148 | return itr->second->countPending(); 149 | } 150 | 151 | void Client::send() { 152 | if (isSending || sendQueue.empty()) 153 | return; 154 | auto &head(sendQueue.front()); 155 | auto dumped(std::make_shared()); 156 | { 157 | std::vector p; 158 | for (auto &action : head) 159 | action->dump(std::get(p.emplace_back(std::in_place_type))); 160 | *dumped = serialize(arrayToSTable(std::move(p))); 161 | } 162 | isSending = true; 163 | for (auto &action : head) 164 | responseQueue.emplace_back(std::move(action)); 165 | sendQueueTotal -= head.size(); 166 | sendQueue.pop_front(); 167 | #if DUMP_PACKETS 168 | std::cout << logHeader << " <=< " << *dumped << std::endl; 169 | #endif 170 | boost::asio::async_write(socket, boost::asio::buffer(*dumped), 171 | makeWeakCallback(weak_from_this(), [this, dumped](const boost::system::error_code &ec, size_t) { 172 | if (ec) { 173 | log("error writing: " + ec.message()); 174 | s.removeClient(*this); 175 | } else { 176 | isSending = false; 177 | send(); 178 | } 179 | }) 180 | ); 181 | updateTimer(false); 182 | } 183 | 184 | void Client::enqueueActionGroup(std::vector actions) { 185 | sendQueueTotal += actions.size(); 186 | sendQueue.emplace_back(std::move(actions)); 187 | send(); 188 | } 189 | 190 | size_t Client::countPending() const { 191 | return sendQueueTotal + responseQueue.size(); 192 | } 193 | 194 | void Client::updateTimer(bool restart) { 195 | if (sendQueue.empty() && responseQueue.empty()) { 196 | responseTimer.reset(); 197 | } else if (restart || !responseTimer) { 198 | responseTimer = std::make_shared(s.io.io); 199 | responseTimer->expires_after(timeout()); 200 | responseTimer->async_wait(makeWeakCallback(responseTimer, [this](const boost::system::error_code&) { 201 | log("timeout waiting for response"); 202 | s.removeClient(*this); 203 | })); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /server/CPPImpl/Server.h: -------------------------------------------------------------------------------- 1 | #ifndef _SERVER_H_ 2 | #define _SERVER_H_ 3 | #include 4 | #include 5 | #include 6 | #include "Actions.h" 7 | 8 | struct Server; 9 | struct Client; 10 | 11 | struct IOEnv { 12 | boost::asio::io_context io; 13 | boost::asio::io_context::strand strand; 14 | IOEnv() :io(), strand(io) {} 15 | template 16 | void operator()(T &&fn) { 17 | boost::asio::post(strand, std::forward(fn)); 18 | } 19 | }; 20 | 21 | struct Client : std::enable_shared_from_this { 22 | using Itr = std::list>::iterator; 23 | static constexpr auto timeout() { return std::chrono::seconds{30}; } 24 | private: 25 | Server &s; 26 | Itr itr; 27 | boost::asio::ip::tcp::socket socket; 28 | std::optional login; 29 | std::string logHeader; 30 | std::list> sendQueue; 31 | std::list responseQueue; 32 | std::shared_ptr responseTimer; 33 | bool isSending = false; 34 | size_t sendQueueTotal{}; 35 | Deserializer d; 36 | void onPacket(SValue); 37 | void read(); 38 | void send(); 39 | void updateTimer(bool restart); 40 | public: 41 | ~Client(); 42 | Client(Server &s, boost::asio::ip::tcp::socket socket); 43 | const auto &getLogin() const { return login; } 44 | const auto &getItr() const { return itr; } 45 | void init(const Itr&); 46 | void log(const std::string &message); 47 | void enqueueActionGroup(std::vector actions); 48 | size_t countPending() const; 49 | }; 50 | 51 | struct Access { 52 | std::string client; 53 | Access(std::string client) 54 | :client(std::move(client)) {} 55 | }; 56 | 57 | struct Server { 58 | IOEnv &io; 59 | private: 60 | std::shared_ptr alive{std::make_shared()}; 61 | boost::asio::ip::tcp::acceptor acceptor; 62 | std::list> clients; 63 | std::unordered_map logins; 64 | void accept(); 65 | public: 66 | Server(IOEnv &io, uint16_t port); 67 | void updateLogin(Client &c); 68 | void removeClient(Client &c); 69 | void enqueueActionGroup(const std::string &client, std::vector actions); 70 | void enqueueAction(const std::string &client, SharedAction action); 71 | size_t countPending(const std::string &client) const; 72 | 73 | template 74 | const T &getBestAccess(const std::vector &accesses) { 75 | const T *bestAccess(&accesses.front()); 76 | size_t bestCount(std::numeric_limits::max()); 77 | for (auto &access : accesses) { 78 | size_t count(countPending(access.client)); 79 | if (count < bestCount) { 80 | bestCount = count; 81 | bestAccess = &access; 82 | } 83 | } 84 | return *bestAccess; 85 | } 86 | }; 87 | 88 | #endif 89 | -------------------------------------------------------------------------------- /server/CPPImpl/Storages.cpp: -------------------------------------------------------------------------------- 1 | #include "Storages.h" 2 | 3 | SharedPromise StorageDrawer::update() { 4 | auto &access(factory.s.getBestAccess(accesses)); 5 | auto action(std::make_shared()); 6 | action->inv = access.addr; 7 | action->side = access.sideInv; 8 | factory.s.enqueueAction(access.client, action); 9 | return action->map(factory.alive, [this](std::vector &&items) { 10 | for (size_t slot{}; slot < items.size(); ++slot) { 11 | auto &stack(items[slot]); 12 | if (stack) 13 | factory.getOrAddItemInfo(stack->item).addProvider( 14 | std::make_unique(*this, slot, stack->size)); 15 | } 16 | return std::monostate{}; 17 | }); 18 | } 19 | 20 | std::optional StorageDrawer::sinkPriority(const Item &item) { 21 | for (auto &i : filters) 22 | if (i->filter(item)) 23 | return std::numeric_limits::max(); 24 | return std::nullopt; 25 | } 26 | 27 | std::pair> StorageDrawer::sink(const ItemStack &stack, size_t slot) { 28 | auto &access(factory.s.getBestAccess(accesses)); 29 | auto action(std::make_shared()); 30 | action->inv = access.addr; 31 | action->fn = "transferItem"; 32 | action->args = { 33 | static_cast(access.sideBus), 34 | static_cast(access.sideInv), 35 | static_cast(stack.size), 36 | static_cast(slot + 1) 37 | }; 38 | factory.s.enqueueAction(access.client, action); 39 | return {stack.size, action->mapTo(std::monostate{})}; 40 | } 41 | 42 | SharedPromise ProviderDrawer::extract(int size, size_t slot) { 43 | auto &access(factory.s.getBestAccess(drawer.accesses)); 44 | auto action(std::make_shared()); 45 | action->inv = access.addr; 46 | action->fn = "transferItem"; 47 | action->args = { 48 | static_cast(access.sideInv), 49 | static_cast(access.sideBus), 50 | static_cast(size), 51 | static_cast(this->slot + 1), 52 | static_cast(slot + 1) 53 | }; 54 | factory.s.enqueueAction(access.client, action); 55 | return action->mapTo(std::monostate{}); 56 | } 57 | 58 | SharedPromise StorageChest::update() { 59 | auto &access(factory.s.getBestAccess(accesses)); 60 | auto action(std::make_shared()); 61 | action->inv = access.addr; 62 | action->side = access.sideInv; 63 | factory.s.enqueueAction(access.client, action); 64 | return action->map(factory.alive, [this](std::vector &&items) { 65 | content = std::move(items); 66 | for (size_t slot{}; slot < content.size(); ++slot) { 67 | auto &stack(content[slot]); 68 | if (stack) 69 | factory.getOrAddItemInfo(stack->item).addProvider( 70 | std::make_unique(*this, slot, stack->size)); 71 | } 72 | return std::monostate{}; 73 | }); 74 | } 75 | 76 | std::optional StorageChest::sinkPriority(const Item &item) { 77 | std::optional emptySlot; 78 | std::optional maxSize; 79 | for (size_t slot{}; slot < content.size(); ++slot) { 80 | auto &stack(content[slot]); 81 | if (stack) { 82 | if (*stack->item == item && stack->size < item.maxSize) { 83 | if (!maxSize.has_value() || stack->size > *maxSize) { 84 | maxSize = stack->size; 85 | slotToSink = slot; 86 | } 87 | } 88 | } else { 89 | emptySlot.emplace(slot); 90 | } 91 | } 92 | if (maxSize.has_value()) { 93 | return maxSize; 94 | } else if (emptySlot.has_value()) { 95 | slotToSink = *emptySlot; 96 | return std::numeric_limits::min() + 1; 97 | } else { 98 | return std::nullopt; 99 | } 100 | } 101 | 102 | std::pair> StorageChest::sink(const ItemStack &stack, size_t slot) { 103 | int toProc; 104 | auto &dstStack(content[slotToSink]); 105 | if (dstStack) { 106 | toProc = std::min(stack.size, dstStack->item->maxSize - dstStack->size); 107 | dstStack->size += toProc; 108 | } else { 109 | toProc = stack.size; 110 | dstStack = std::make_shared(stack); 111 | } 112 | auto &access(factory.s.getBestAccess(accesses)); 113 | auto action(std::make_shared()); 114 | action->inv = access.addr; 115 | action->fn = "transferItem"; 116 | action->args = { 117 | static_cast(access.sideBus), 118 | static_cast(access.sideInv), 119 | static_cast(toProc), 120 | static_cast(slot + 1), 121 | static_cast(slotToSink + 1) 122 | }; 123 | factory.s.enqueueAction(access.client, action); 124 | return {toProc, action->mapTo(std::monostate{})}; 125 | } 126 | 127 | SharedPromise ProviderChest::extract(int size, size_t slot) { 128 | auto &access(factory.s.getBestAccess(chest.accesses)); 129 | auto action(std::make_shared()); 130 | action->inv = access.addr; 131 | action->fn = "transferItem"; 132 | action->args = { 133 | static_cast(access.sideInv), 134 | static_cast(access.sideBus), 135 | static_cast(size), 136 | static_cast(this->slot + 1), 137 | static_cast(slot + 1) 138 | }; 139 | factory.s.enqueueAction(access.client, action); 140 | return action->map(factory.alive, [&chest(chest), size, slot(this->slot)](auto&&) { 141 | auto &stack(chest.content[slot]); 142 | stack->size -= size; 143 | if (stack->size <= 0) 144 | stack.reset(); 145 | return std::monostate{}; 146 | }); 147 | } 148 | 149 | SharedPromise StorageME::update() { 150 | auto &access(factory.s.getBestAccess(accesses)); 151 | auto action(std::make_shared()); 152 | action->inv = access.me; 153 | factory.s.enqueueAction(access.client, action); 154 | return action->map(factory.alive, [this](std::vector &&items) { 155 | for (auto &stack : items) { 156 | stack->item->others.erase("isCraftable"); 157 | factory.getOrAddItemInfo(stack->item).addProvider( 158 | std::make_unique(*this, stack->item, stack->size)); 159 | } 160 | return std::monostate{}; 161 | }); 162 | } 163 | 164 | std::pair> StorageME::sink(const ItemStack &stack, size_t slot) { 165 | auto &access(factory.s.getBestAccess(accesses)); 166 | auto action(std::make_shared()); 167 | action->inv = access.addr; 168 | action->fn = "transferItem"; 169 | action->args = { 170 | static_cast(access.sideBus), 171 | static_cast(access.sideInv), 172 | static_cast(stack.size), 173 | static_cast(slot + 1), 174 | 9.0 175 | }; 176 | factory.s.enqueueAction(access.client, action); 177 | return {stack.size, action->mapTo(std::monostate{})}; 178 | } 179 | 180 | SharedPromise ProviderME::extract(int size, size_t slot) { 181 | auto itr(me.accessForItem.try_emplace(item)); 182 | if (itr.second) 183 | itr.first->second = &factory.s.getBestAccess(me.accesses); 184 | auto &access(*itr.first->second); 185 | auto action(std::make_shared()); 186 | item->serialize(action->filter); 187 | action->size = size; 188 | action->inv = access.addr; 189 | action->me = access.me; 190 | action->entry = access.entry; 191 | action->args = { 192 | static_cast(access.sideInv), 193 | static_cast(access.sideBus), 194 | static_cast(size), 195 | static_cast(access.entry), 196 | static_cast(slot + 1) 197 | }; 198 | factory.s.enqueueAction(access.client, action); 199 | return action->mapTo(std::monostate{}); 200 | } 201 | -------------------------------------------------------------------------------- /server/CPPImpl/Storages.h: -------------------------------------------------------------------------------- 1 | #ifndef _STORAGES_H_ 2 | #define _STORAGES_H_ 3 | #include "Factory.h" 4 | 5 | struct StorageDrawer : public Storage { 6 | std::vector accesses; 7 | std::vector filters; 8 | StorageDrawer(Factory &factory, decltype(accesses) accesses, decltype(filters) filters) 9 | :Storage(factory), accesses(std::move(accesses)), filters(std::move(filters)) {} 10 | SharedPromise update() override; 11 | std::optional sinkPriority(const Item&) override; 12 | std::pair> sink(const ItemStack&, size_t slot) override; 13 | }; 14 | 15 | struct ProviderDrawer : Provider { 16 | StorageDrawer &drawer; 17 | size_t slot; 18 | ProviderDrawer(StorageDrawer &drawer, size_t slot, int size) 19 | :Provider(drawer.factory, size, std::numeric_limits::min()), drawer(drawer), slot(slot) {} 20 | SharedPromise extract(int size, size_t slot) override; 21 | }; 22 | 23 | struct StorageChest : Storage { 24 | std::vector accesses; 25 | std::vector content; 26 | size_t slotToSink; 27 | StorageChest(Factory &factory, decltype(accesses) accesses) 28 | :Storage(factory), accesses(std::move(accesses)) {} 29 | void endOfCycle() override { content.clear(); } 30 | SharedPromise update() override; 31 | std::optional sinkPriority(const Item&) override; 32 | std::pair> sink(const ItemStack&, size_t slot) override; 33 | }; 34 | 35 | struct ProviderChest : Provider { 36 | StorageChest &chest; 37 | size_t slot; 38 | ProviderChest(StorageChest &chest, size_t slot, int size) 39 | :Provider(chest.factory, size, -size), chest(chest), slot(slot) {} 40 | SharedPromise extract(int size, size_t slot) override; 41 | }; 42 | 43 | struct AccessME : AccessInv { 44 | std::string me; 45 | int entry; 46 | AccessME(std::string client, std::string addr, int sideInv, int sideBus, int entry, std::string me = "me_interface") 47 | :AccessInv(std::move(client), std::move(addr), sideInv, sideBus), me(std::move(me)), entry(entry) {} 48 | }; 49 | 50 | struct StorageME : Storage { 51 | std::vector accesses; 52 | std::unordered_map, const AccessME*, SharedItemHash, SharedItemEqual> accessForItem; 53 | StorageME(Factory &factory, std::vector accesses) :Storage(factory), accesses(std::move(accesses)) {} 54 | void endOfCycle() override { accessForItem.clear(); } 55 | SharedPromise update() override; 56 | std::optional sinkPriority(const Item&) override { return std::numeric_limits::min(); } 57 | std::pair> sink(const ItemStack&, size_t slot) override; 58 | }; 59 | 60 | struct ProviderME : Provider { 61 | StorageME &me; 62 | SharedItem item; 63 | ProviderME(StorageME &me, SharedItem item, int size) 64 | :Provider(me.factory, size, std::numeric_limits::max()), me(me), item(std::move(item)) {} 65 | SharedPromise extract(int size, size_t slot) override; 66 | }; 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /server/CPPImpl/WeakCallback.h: -------------------------------------------------------------------------------- 1 | #ifndef _WEAK_CALLBACK_H_ 2 | #define _WEAK_CALLBACK_H_ 3 | #include 4 | 5 | template 6 | class WeakCallback { 7 | static_assert(sizeof(ThisT) < 0, "deduction Failed"); 8 | }; 9 | 10 | template 11 | class WeakCallback { 12 | std::weak_ptr wkThis; 13 | CallbackObjectT callback; 14 | public: 15 | WeakCallback(std::weak_ptr wkThis, CallbackObjectT callback) 16 | :wkThis(std::move(wkThis)), callback(std::move(callback)) {} 17 | 18 | void operator()(Ts ...xs) const { 19 | auto pThis(wkThis.lock()); 20 | if (!pThis) return; 21 | callback(std::forward(xs)...); 22 | } 23 | }; 24 | 25 | template 26 | inline WeakCallback 27 | makeWeakCallback(std::shared_ptr pThis, CallbackObjectT callback) { 28 | return {std::move(pThis), std::move(callback)}; 29 | } 30 | 31 | template 32 | inline WeakCallback 33 | makeWeakCallback(std::weak_ptr pThis, CallbackObjectT callback) { 34 | return {std::move(pThis), std::move(callback)}; 35 | } 36 | 37 | template 38 | class WeakDeferredCallback { 39 | static_assert(sizeof(ThisT) < 0, "deduction Failed"); 40 | }; 41 | 42 | template 43 | class WeakDeferredCallbackTask { 44 | std::weak_ptr wkThis; 45 | std::shared_ptr callback; 46 | mutable std::tuple arguments; 47 | 48 | template 49 | void helper(std::integer_sequence) const { 50 | callback->operator()(std::forward(std::get(arguments))...); 51 | } 52 | public: 53 | WeakDeferredCallbackTask( 54 | std::weak_ptr wkThis, 55 | std::shared_ptr callback, 56 | std::tuple arguments) 57 | :wkThis(std::move(wkThis)), 58 | callback(std::move(callback)), 59 | arguments(std::move(arguments)) {} 60 | 61 | void operator()() const { 62 | auto pThis(wkThis.lock()); 63 | if (!pThis) return; 64 | helper(std::index_sequence_for()); 65 | } 66 | }; 67 | 68 | template 69 | class WeakDeferredCallback { 70 | std::weak_ptr wkThis; 71 | QueueT queue; 72 | std::shared_ptr callback; 73 | public: 74 | WeakDeferredCallback(std::weak_ptr wkThis, QueueT queue, CallbackObjectT callback) 75 | :wkThis(std::move(wkThis)), queue(std::move(queue)), 76 | callback(std::make_shared(std::move(callback))) {} 77 | 78 | void operator()(Ts ...xs) const { 79 | queue(WeakDeferredCallbackTask{ 80 | wkThis, 81 | callback, 82 | std::tuple(std::forward(xs)...) 83 | }); 84 | } 85 | }; 86 | 87 | template 88 | inline WeakDeferredCallback 89 | makeWeakDeferredCallback(std::shared_ptr pThis, QueueT queue, CallbackObjectT callback) { 90 | return {std::move(pThis), std::move(queue), std::move(callback)}; 91 | } 92 | 93 | template 94 | inline WeakDeferredCallback 95 | makeWeakDeferredCallback(std::weak_ptr pThis, QueueT queue, CallbackObjectT callback) { 96 | return {std::move(pThis), std::move(queue), std::move(callback)}; 97 | } 98 | 99 | template 100 | class DeferredCallbackTask { 101 | std::shared_ptr callback; 102 | mutable std::tuple arguments; 103 | 104 | template 105 | void helper(std::integer_sequence) const { 106 | callback->operator()(std::forward(std::get(arguments))...); 107 | } 108 | public: 109 | DeferredCallbackTask( 110 | std::shared_ptr callback, 111 | std::tuple arguments) 112 | :callback(std::move(callback)), 113 | arguments(std::move(arguments)) {} 114 | 115 | void operator()() const { 116 | helper(std::index_sequence_for()); 117 | } 118 | }; 119 | 120 | template 121 | class DeferredCallback { 122 | static_assert(sizeof(QueueT) < 0, "deduction Failed"); 123 | }; 124 | 125 | template 126 | class DeferredCallback { 127 | QueueT queue; 128 | std::shared_ptr callback; 129 | public: 130 | DeferredCallback(QueueT queue, CallbackObjectT callback) 131 | :queue(std::move(queue)), 132 | callback(std::make_shared(std::move(callback))) {} 133 | 134 | void operator()(Ts ...xs) const { 135 | queue(DeferredCallbackTask{ 136 | callback, 137 | std::tuple(std::forward(xs)...) 138 | }); 139 | } 140 | }; 141 | 142 | template 143 | inline DeferredCallback 144 | makeDeferredCallback(QueueT queue, CallbackObjectT callback) { 145 | return {std::move(queue), std::move(callback)}; 146 | } 147 | 148 | #endif 149 | -------------------------------------------------------------------------------- /server/RustImpl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oc-remote" 3 | version = "0.1.0" 4 | authors = ["Yibo Cao "] 5 | edition = "2021" 6 | 7 | [features] 8 | dump_traffic = [] 9 | 10 | [dependencies] 11 | tokio = { version = "1", features = ["rt", "net", "time", "macros", "sync", "io-util"] } 12 | abort-on-drop = "0" 13 | ordered-float = "2" 14 | futures-util = "0" 15 | num-traits = "0" 16 | socket2 = "0" 17 | flexstr = "0" 18 | fnv = "1" 19 | ratatui = "0.28.1" 20 | crossterm = { version = "0.28.1", features = ["event-stream"] } 21 | tui-textarea = "0.6.1" 22 | regex = "1.10.4" 23 | 24 | [profile.dev] 25 | panic = "abort" 26 | 27 | [profile.release] 28 | panic = "abort" 29 | -------------------------------------------------------------------------------- /server/RustImpl/rustfmt.toml: -------------------------------------------------------------------------------- 1 | use_small_heuristics = "Max" 2 | fn_single_line = true 3 | max_width = 120 4 | -------------------------------------------------------------------------------- /server/RustImpl/src/access.rs: -------------------------------------------------------------------------------- 1 | use flexstr::LocalStr; 2 | 3 | pub trait Access { 4 | fn get_client(&self) -> &str; 5 | } 6 | 7 | macro_rules! impl_access { 8 | ($i:ident) => { 9 | impl Access for $i { 10 | fn get_client(&self) -> &str { &*self.client } 11 | } 12 | }; 13 | } 14 | 15 | impl_access!(SidedAccess); 16 | pub struct SidedAccess { 17 | pub client: LocalStr, 18 | pub addr: LocalStr, 19 | pub side: u8, 20 | } 21 | 22 | impl_access!(InvAccess); 23 | pub struct InvAccess { 24 | pub client: LocalStr, 25 | pub addr: LocalStr, 26 | pub bus_side: u8, 27 | pub inv_side: u8, 28 | } 29 | 30 | impl_access!(MEAccess); 31 | pub struct MEAccess { 32 | pub client: LocalStr, 33 | pub transposer_addr: LocalStr, 34 | pub me_addr: LocalStr, 35 | pub bus_side: u8, 36 | pub me_side: u8, 37 | // 0-7 are valid. 8 is for deposit. 38 | pub me_slot: usize, 39 | } 40 | 41 | impl_access!(ComponentAccess); 42 | pub struct ComponentAccess { 43 | pub client: LocalStr, 44 | // typical address for reactor: br_reactor 45 | // typical address for plastic mixer: plastic_mixer 46 | // typical address for flux controller: flux_controller 47 | pub addr: LocalStr, 48 | } 49 | 50 | impl_access!(CraftingRobotAccess); 51 | pub struct CraftingRobotAccess { 52 | pub client: LocalStr, 53 | pub bus_side: u8, 54 | } 55 | 56 | impl_access!(WorkbenchAccess); 57 | pub struct WorkbenchAccess { 58 | pub client: LocalStr, 59 | pub input_addr: LocalStr, 60 | pub output_addr: LocalStr, 61 | pub input_bus_side: u8, 62 | pub output_bus_side: u8, 63 | pub non_consumable_side: u8, 64 | } 65 | 66 | pub struct EachInvAccess { 67 | pub addr: LocalStr, 68 | pub bus_side: u8, 69 | pub inv_side: u8, 70 | } 71 | 72 | impl_access!(MultiInvAccess); 73 | pub struct MultiInvAccess { 74 | pub client: LocalStr, 75 | pub invs: Vec, 76 | } 77 | 78 | #[derive(Clone)] 79 | pub struct EachTank { 80 | pub addr: LocalStr, 81 | pub side: u8, 82 | } 83 | 84 | impl_access!(FluidAccess); 85 | pub struct FluidAccess { 86 | pub client: LocalStr, 87 | pub tanks: Vec, 88 | } 89 | 90 | pub struct EachBusOfTank { 91 | pub addr: LocalStr, 92 | pub bus_side: u8, 93 | pub tank_side: u8, 94 | } 95 | 96 | impl_access!(TankAccess); 97 | pub struct TankAccess { 98 | pub client: LocalStr, 99 | pub buses: Vec, 100 | } 101 | 102 | impl_access!(InvTankAccess); 103 | pub struct InvTankAccess { 104 | pub client: LocalStr, 105 | pub invs: Vec, 106 | pub tanks: Vec>, 107 | } 108 | -------------------------------------------------------------------------------- /server/RustImpl/src/action.rs: -------------------------------------------------------------------------------- 1 | use super::item::ItemStack; 2 | use super::lua_value::{table_to_vec, vec_to_table, Table, Value}; 3 | use flexstr::LocalStr; 4 | use std::{ 5 | cell::RefCell, 6 | future::Future, 7 | pin::Pin, 8 | rc::Rc, 9 | task::{Context, Poll, Waker}, 10 | }; 11 | 12 | pub trait Action: 'static { 13 | type Output; 14 | fn build_request(self) -> Value; 15 | fn parse_response(response: Value) -> Result; 16 | } 17 | 18 | struct ActionState { 19 | result: Option>, 20 | waker: Option, 21 | action: Option, 22 | } 23 | 24 | pub trait ActionRequest { 25 | fn build_request(&mut self) -> Value; 26 | fn on_fail(&mut self, reason: LocalStr); 27 | fn on_response(&mut self, result: Value) -> Result<(), LocalStr>; 28 | } 29 | 30 | impl ActionRequest for ActionState { 31 | fn build_request(&mut self) -> Value { self.action.take().unwrap().build_request() } 32 | 33 | fn on_fail(&mut self, reason: LocalStr) { 34 | self.result = Some(Err(reason)); 35 | if let Some(waker) = self.waker.take() { 36 | waker.wake() 37 | } 38 | } 39 | 40 | fn on_response(&mut self, result: Value) -> Result<(), LocalStr> { 41 | let result = T::parse_response(result); 42 | let ret = if let Err(ref e) = result { Err(e.clone()) } else { Ok(()) }; 43 | self.result = Some(result); 44 | if let Some(waker) = self.waker.take() { 45 | waker.wake() 46 | } 47 | ret 48 | } 49 | } 50 | 51 | pub struct ActionFuture(Rc>>); 52 | 53 | impl Clone for ActionFuture { 54 | fn clone(&self) -> Self { ActionFuture(self.0.clone()) } 55 | } 56 | 57 | impl Future for ActionFuture { 58 | type Output = Result; 59 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 60 | let this = self.get_mut(); 61 | let mut this = this.0.borrow_mut(); 62 | if let Some(result) = this.result.take() { 63 | Poll::Ready(result) 64 | } else { 65 | this.waker = Some(cx.waker().clone()); 66 | Poll::Pending 67 | } 68 | } 69 | } 70 | 71 | impl From for ActionFuture { 72 | fn from(action: T) -> Self { 73 | ActionFuture(Rc::new(RefCell::new(ActionState { result: None, waker: None, action: Some(action) }))) 74 | } 75 | } 76 | 77 | impl From> for Rc> { 78 | fn from(future: ActionFuture) -> Self { future.0 } 79 | } 80 | 81 | #[derive(Clone)] 82 | pub struct Print { 83 | pub text: LocalStr, 84 | pub color: u32, 85 | pub beep: Option, 86 | } 87 | 88 | impl Action for Print { 89 | type Output = (); 90 | 91 | fn build_request(self) -> Value { 92 | let mut result = Table::new(); 93 | result.insert("op".into(), "print".into()); 94 | result.insert("color".into(), self.color.into()); 95 | result.insert("text".into(), self.text.into()); 96 | if let Some(beep) = self.beep { 97 | result.insert("beep".into(), beep.into()); 98 | } 99 | result.into() 100 | } 101 | 102 | fn parse_response(_: Value) -> Result<(), LocalStr> { Ok(()) } 103 | } 104 | 105 | pub struct List { 106 | pub addr: LocalStr, 107 | pub side: u8, 108 | } 109 | 110 | impl Action for List { 111 | type Output = Vec>; 112 | 113 | fn build_request(self) -> Value { 114 | let mut result = Table::new(); 115 | result.insert("op".into(), "list".into()); 116 | result.insert("side".into(), self.side.into()); 117 | result.insert("inv".into(), self.addr.into()); 118 | result.into() 119 | } 120 | 121 | fn parse_response(response: Value) -> Result>, LocalStr> { 122 | table_to_vec(response.try_into()?)? 123 | .into_iter() 124 | .map(|x| match x { 125 | Value::N => Ok(None), 126 | Value::S(_) => Ok(None), 127 | x => ItemStack::parse(x).map(|x| Some(x)), 128 | }) 129 | .collect() 130 | } 131 | } 132 | 133 | pub struct ListME { 134 | pub addr: LocalStr, 135 | } 136 | 137 | impl Action for ListME { 138 | type Output = Vec; 139 | 140 | fn build_request(self) -> Value { 141 | let mut result = Table::new(); 142 | result.insert("op".into(), "listME".into()); 143 | result.insert("inv".into(), self.addr.into()); 144 | result.into() 145 | } 146 | 147 | fn parse_response(response: Value) -> Result, LocalStr> { 148 | table_to_vec(response.try_into()?)?.into_iter().map(|x| ItemStack::parse(x)).collect() 149 | } 150 | } 151 | 152 | pub struct XferME { 153 | pub me_addr: LocalStr, 154 | pub me_slot: usize, 155 | pub filter: Value, 156 | pub size: i32, 157 | pub transposer_addr: LocalStr, 158 | pub transposer_args: Vec, 159 | } 160 | 161 | impl Action for XferME { 162 | type Output = (); 163 | 164 | fn build_request(self) -> Value { 165 | let mut result = Table::new(); 166 | result.insert("op".into(), "xferME".into()); 167 | result.insert("me".into(), self.me_addr.into()); 168 | result.insert("entry".into(), (self.me_slot + 1).into()); 169 | result.insert("filter".into(), self.filter); 170 | result.insert("size".into(), self.size.into()); 171 | result.insert("inv".into(), self.transposer_addr.into()); 172 | result.insert("args".into(), vec_to_table(self.transposer_args).into()); 173 | result.into() 174 | } 175 | 176 | fn parse_response(_: Value) -> Result<(), LocalStr> { Ok(()) } 177 | } 178 | 179 | pub struct Call { 180 | pub addr: LocalStr, 181 | pub func: LocalStr, 182 | pub args: Vec, 183 | } 184 | 185 | impl Action for Call { 186 | type Output = Value; 187 | 188 | fn build_request(self) -> Value { 189 | let mut result = Table::new(); 190 | result.insert("op".into(), "call".into()); 191 | result.insert("inv".into(), self.addr.into()); 192 | result.insert("fn".into(), self.func.into()); 193 | result.insert("args".into(), vec_to_table(self.args).into()); 194 | result.into() 195 | } 196 | 197 | fn parse_response(response: Value) -> Result { Ok(response) } 198 | } 199 | -------------------------------------------------------------------------------- /server/RustImpl/src/config.rs: -------------------------------------------------------------------------------- 1 | use crate::factory::{Factory, FactoryConfig}; 2 | use crate::{access::*, config_util::*, process::*, recipe::*, side::*, storage::*}; 3 | use crate::{server::Server, Tui}; 4 | use std::{cell::RefCell, rc::Rc, time::Duration}; 5 | 6 | pub fn build_factory(tui: Rc) -> Rc> { 7 | FactoryConfig { 8 | tui: tui.clone(), 9 | server: Server::new(tui, 1847), 10 | min_cycle_time: Duration::from_secs(1), 11 | log_clients: vec![s("main")], 12 | bus_accesses: vec![SidedAccess { client: s("1a"), addr: s("538"), side: EAST }], 13 | fluid_bus_accesses: vec![], 14 | fluid_bus_capacity: 0, 15 | backups: vec![(label("Potato"), 32)], 16 | fluid_backups: vec![], 17 | } 18 | .build(|factory| { 19 | factory.add_process(ManualUiConfig { accesses: vec![] }); 20 | factory.add_storage(ChestConfig { 21 | accesses: vec![InvAccess { client: s("1a"), addr: s("538"), bus_side: EAST, inv_side: UP }], 22 | }); 23 | factory.add_process(BufferedConfig { 24 | name: s("output"), 25 | accesses: vec![InvAccess { client: s("1a"), addr: s("677"), bus_side: EAST, inv_side: UP }], 26 | slot_filter: None, 27 | to_extract: extract_all(), 28 | recipes: vec![], 29 | max_recipe_inputs: 0, 30 | stocks: vec![], 31 | }); 32 | factory.add_process(BufferedConfig { 33 | name: s("stock"), 34 | accesses: vec![InvAccess { client: s("1a"), addr: s("c65"), bus_side: WEST, inv_side: UP }], 35 | slot_filter: None, 36 | to_extract: None, 37 | recipes: vec![], 38 | max_recipe_inputs: 0, 39 | stocks: vec![BufferedInput::new(label("Bio Fuel"), 64), BufferedInput::new(label("Fluxed Phyto-Gro"), 64)], 40 | }); 41 | factory.add_process(BlockingOutputConfig { 42 | accesses: vec![InvAccess { client: s("1a"), addr: s("f59"), bus_side: EAST, inv_side: UP }], 43 | slot_filter: None, 44 | outputs: vec![Output { item: label("Cobblestone"), n_wanted: 64 }], 45 | }); 46 | factory.add_process(SlottedConfig { 47 | name: s("manufactory"), 48 | accesses: vec![InvAccess { client: s("1a"), addr: s("2e2"), bus_side: WEST, inv_side: UP }], 49 | input_slots: vec![0], 50 | to_extract: None, 51 | strict_priority: false, 52 | recipes: vec![ 53 | SlottedRecipe { 54 | outputs: Output::new(label("Sand"), 64), 55 | inputs: vec![SlottedInput::new(label("Cobblestone"), vec![(0, 1)])], 56 | max_sets: 8, 57 | }, 58 | SlottedRecipe { 59 | outputs: Output::new(label("Niter"), 64), 60 | inputs: vec![SlottedInput::new(label("Sandstone"), vec![(0, 1)])], 61 | max_sets: 8, 62 | }, 63 | SlottedRecipe { 64 | outputs: Output::new(label("Pulverized Charcoal"), 64), 65 | inputs: vec![SlottedInput::new(label("Charcoal"), vec![(0, 1)])], 66 | max_sets: 8, 67 | }, 68 | ], 69 | }); 70 | factory.add_process(BufferedConfig { 71 | name: s("crafter"), 72 | accesses: vec![InvAccess { client: s("1a"), addr: s("0c7"), bus_side: EAST, inv_side: UP }], 73 | slot_filter: None, 74 | to_extract: None, 75 | recipes: vec![ 76 | BufferedRecipe { 77 | outputs: Output::new(label("Sandstone"), 64), 78 | inputs: vec![BufferedInput::new(label("Sand"), 4)], 79 | max_inputs: i32::MAX, 80 | }, 81 | BufferedRecipe { 82 | outputs: Output::new(label("Rich Phyto-Gro"), 64), 83 | inputs: vec![ 84 | BufferedInput::new(label("Pulverized Charcoal"), 1), 85 | BufferedInput::new(label("Niter"), 1), 86 | BufferedInput::new(label("Rich Slag"), 1), 87 | ], 88 | max_inputs: i32::MAX, 89 | }, 90 | BufferedRecipe { 91 | outputs: Output::new(label("Compass"), 64), 92 | inputs: vec![BufferedInput::new(label("Iron Ingot"), 4), BufferedInput::new(label("Redstone"), 1)], 93 | max_inputs: i32::MAX, 94 | }, 95 | BufferedRecipe { 96 | outputs: Output::new(label("Redstone"), 64), 97 | inputs: vec![BufferedInput::new(label("Redstone Essence"), 9)], 98 | max_inputs: i32::MAX, 99 | }, 100 | ], 101 | max_recipe_inputs: i32::MAX, 102 | stocks: vec![], 103 | }); 104 | factory.add_process(SlottedConfig { 105 | name: s("charger"), 106 | accesses: vec![InvAccess { client: s("1a"), addr: s("007"), bus_side: WEST, inv_side: UP }], 107 | input_slots: vec![0], 108 | to_extract: None, 109 | strict_priority: false, 110 | recipes: vec![SlottedRecipe { 111 | outputs: Output::new(label("Fluxed Phyto-Gro"), 64), 112 | inputs: vec![SlottedInput::new(label("Rich Phyto-Gro"), vec![(0, 1)])], 113 | max_sets: i32::MAX, 114 | }], 115 | }); 116 | factory.add_process(ScatteringConfig { 117 | name: s("crusher"), 118 | accesses: vec![InvAccess { client: s("1a"), addr: s("525"), bus_side: EAST, inv_side: UP }], 119 | input_slots: vec![0, 1, 2, 3, 4, 5, 6], 120 | to_extract: None, 121 | recipes: vec![ScatteringRecipe::new( 122 | Output::new(label("Bio Fuel"), 64), 123 | ScatteringInput::new(label("Potato")), 124 | )], 125 | max_per_slot: 4, 126 | }); 127 | factory.add_process(ScatteringConfig { 128 | name: s("furnace"), 129 | accesses: vec![InvAccess { client: s("1a"), addr: s("346"), bus_side: WEST, inv_side: UP }], 130 | input_slots: vec![0, 1, 2, 3, 4, 5, 6], 131 | to_extract: None, 132 | recipes: vec![ScatteringRecipe::new( 133 | Output::new(label("Charcoal"), 64), 134 | ScatteringInput::new(label("Birch Wood")), 135 | )], 136 | max_per_slot: 4, 137 | }); 138 | factory.add_process(SlottedConfig { 139 | name: s("phyto"), 140 | accesses: vec![InvAccess { client: s("1a"), addr: s("693"), bus_side: WEST, inv_side: UP }], 141 | input_slots: vec![0], 142 | to_extract: None, 143 | strict_priority: false, 144 | recipes: vec![ 145 | SlottedRecipe { 146 | outputs: Output::new(label("Potato"), 64), 147 | inputs: vec![SlottedInput::new(label("Potato"), vec![(0, 1)]).allow_backup()], 148 | max_sets: 4, 149 | }, 150 | SlottedRecipe { 151 | outputs: Output::new(label("Redstone Essence"), 64), 152 | inputs: vec![SlottedInput::new(label("Redstone Seeds"), vec![(0, 1)])], 153 | max_sets: 4, 154 | }, 155 | SlottedRecipe { 156 | outputs: Output::new(label("Birch Wood"), 64), 157 | inputs: vec![SlottedInput::new(label("Birch Sapling"), vec![(0, 1)])], 158 | max_sets: 4, 159 | }, 160 | ], 161 | }); 162 | factory.add_process(SlottedConfig { 163 | name: s("induction"), 164 | accesses: vec![InvAccess { client: s("1a"), addr: s("0f5"), bus_side: EAST, inv_side: UP }], 165 | input_slots: vec![0, 1], 166 | to_extract: None, 167 | strict_priority: false, 168 | recipes: vec![SlottedRecipe { 169 | outputs: Output::new(label("Rich Slag"), 64), 170 | inputs: vec![ 171 | SlottedInput::new(label("Sand"), vec![(0, 1)]), 172 | SlottedInput::new(label("Compass"), vec![(1, 1)]), 173 | ], 174 | max_sets: 8, 175 | }], 176 | }); 177 | factory.add_process(BufferedConfig { 178 | name: s("trash"), 179 | accesses: vec![InvAccess { client: s("1a"), addr: s("c65"), bus_side: WEST, inv_side: NORTH }], 180 | slot_filter: None, 181 | to_extract: None, 182 | recipes: vec![ 183 | BufferedRecipe { 184 | outputs: ignore_outputs(0.), 185 | inputs: vec![BufferedInput::new(label("Poisonous Potato"), 1).extra_backup(64)], 186 | max_inputs: i32::MAX, 187 | }, 188 | BufferedRecipe { 189 | outputs: ignore_outputs(0.), 190 | inputs: vec![BufferedInput::new(label("Redstone Seeds"), 1).extra_backup(64)], 191 | max_inputs: i32::MAX, 192 | }, 193 | BufferedRecipe { 194 | outputs: ignore_outputs(0.), 195 | inputs: vec![BufferedInput::new(label("Birch Sapling"), 1).extra_backup(64)], 196 | max_inputs: i32::MAX, 197 | }, 198 | ], 199 | max_recipe_inputs: i32::MAX, 200 | stocks: vec![], 201 | }) 202 | }) 203 | } 204 | -------------------------------------------------------------------------------- /server/RustImpl/src/config_util.rs: -------------------------------------------------------------------------------- 1 | use super::item::{Filter, Item}; 2 | use flexstr::LocalStr; 3 | use std::rc::Rc; 4 | 5 | pub fn s(x: &'static str) -> LocalStr { LocalStr::from_static(x) } 6 | pub fn label(x: &'static str) -> Filter { Filter::Label(s(x)) } 7 | pub fn name(x: &'static str) -> Filter { Filter::Name(s(x)) } 8 | pub fn both(label: &'static str, name: &'static str) -> Filter { Filter::Both { label: s(label), name: s(name) } } 9 | 10 | pub fn custom(desc: &'static str, func: impl Fn(&Item) -> bool + 'static) -> Filter { 11 | Filter::Custom { desc: s(desc), func: Rc::new(func) } 12 | } 13 | 14 | macro_rules! label { 15 | ($($t:tt)*) => { 16 | Filter::Label(local_fmt!($($t)*)) 17 | }; 18 | } 19 | 20 | macro_rules! name { 21 | ($($t:tt)*) => { 22 | Filter::Name(local_fmt!($($t)*)) 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /server/RustImpl/src/item.rs: -------------------------------------------------------------------------------- 1 | use super::lua_value::{table_remove, Table, Value}; 2 | use flexstr::LocalStr; 3 | use std::{cmp::min, rc::Rc}; 4 | 5 | #[derive(PartialEq, Eq, Hash)] 6 | pub struct Item { 7 | pub label: LocalStr, 8 | pub name: LocalStr, 9 | pub damage: i16, 10 | pub max_damage: i16, 11 | pub max_size: i32, 12 | pub has_tag: bool, 13 | pub others: Table, 14 | } 15 | 16 | impl Item { 17 | pub fn serialize(&self) -> Value { 18 | let mut result = self.others.clone(); 19 | result.insert("label".into(), self.label.clone().into()); 20 | result.insert("name".into(), self.name.clone().into()); 21 | result.insert("damage".into(), self.damage.into()); 22 | result.insert("maxDamage".into(), self.max_damage.into()); 23 | result.insert("maxSize".into(), self.max_size.into()); 24 | result.insert("hasTag".into(), self.has_tag.into()); 25 | result.into() 26 | } 27 | } 28 | 29 | pub fn jammer() -> Rc { 30 | thread_local!(static ITEM: Rc = Rc::new(Item { 31 | label: <_>::default(), 32 | name: <_>::default(), 33 | damage: 0, 34 | max_damage: 0, 35 | max_size: 1, 36 | has_tag: false, 37 | others: Table::new() 38 | })); 39 | ITEM.with(|item| item.clone()) 40 | } 41 | 42 | #[derive(Clone)] 43 | pub struct ItemStack { 44 | pub item: Rc, 45 | pub size: i32, 46 | } 47 | 48 | impl ItemStack { 49 | pub fn parse(value: Value) -> Result { 50 | let mut table: Table = value.try_into()?; 51 | let size = table_remove(&mut table, "size")?; 52 | let label = table_remove(&mut table, "label")?; 53 | let name = table_remove(&mut table, "name")?; 54 | let damage = table_remove(&mut table, "damage")?; 55 | let max_damage = table_remove(&mut table, "maxDamage")?; 56 | let max_size = table_remove(&mut table, "maxSize")?; 57 | let has_tag = table_remove(&mut table, "hasTag")?; 58 | Ok(ItemStack { 59 | item: Rc::new(Item { label, name, damage, max_damage, max_size, has_tag, others: table }), 60 | size, 61 | }) 62 | } 63 | } 64 | 65 | pub struct InsertPlan { 66 | pub n_inserted: i32, 67 | pub insertions: Vec<(usize, i32)>, 68 | } 69 | 70 | pub fn insert_into_inventory(inventory: &mut Vec>, item: &Rc, to_insert: i32) -> InsertPlan { 71 | let mut result = InsertPlan { n_inserted: 0, insertions: Vec::new() }; 72 | let mut remaining = min(to_insert, item.max_size); 73 | let mut first_empty_slot = None; 74 | for (slot, stack) in inventory.iter_mut().enumerate() { 75 | if remaining <= 0 { 76 | return result; 77 | } 78 | if let Some(stack) = stack { 79 | if stack.item == *item { 80 | let to_insert = min(remaining, item.max_size - stack.size); 81 | if to_insert > 0 { 82 | stack.size += to_insert; 83 | result.n_inserted += to_insert; 84 | result.insertions.push((slot, to_insert)); 85 | remaining -= to_insert 86 | } 87 | } 88 | } else if first_empty_slot.is_none() { 89 | first_empty_slot = Some(slot) 90 | } 91 | } 92 | if remaining > 0 { 93 | if let Some(slot) = first_empty_slot { 94 | inventory[slot] = Some(ItemStack { item: item.clone(), size: remaining }); 95 | result.n_inserted += remaining; 96 | result.insertions.push((slot, remaining)) 97 | } 98 | } 99 | result 100 | } 101 | 102 | #[derive(Clone)] 103 | pub enum Filter { 104 | Label(LocalStr), 105 | Name(LocalStr), 106 | Both { label: LocalStr, name: LocalStr }, 107 | Custom { desc: LocalStr, func: Rc bool> }, 108 | } 109 | 110 | impl Filter { 111 | pub fn apply(&self, item: &Item) -> bool { 112 | match self { 113 | Filter::Label(label) => item.label == *label, 114 | Filter::Name(name) => item.name == *name, 115 | Filter::Both { label, name } => item.label == *label && item.name == *name, 116 | Filter::Custom { func, .. } => func(item), 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /server/RustImpl/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | pub mod util; 3 | #[macro_use] 4 | pub mod recipe; 5 | #[macro_use] 6 | pub mod config_util; 7 | pub mod access; 8 | pub mod action; 9 | pub mod config; 10 | pub mod factory; 11 | pub mod item; 12 | pub mod lua_value; 13 | pub mod process; 14 | pub mod server; 15 | pub mod side; 16 | pub mod storage; 17 | 18 | use config::build_factory; 19 | use crossterm::{ 20 | event::{Event, EventStream}, 21 | terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, 22 | ExecutableCommand, 23 | }; 24 | use futures_util::StreamExt; 25 | use ratatui::{ 26 | backend::CrosstermBackend, 27 | layout::{Constraint, Layout, Margin}, 28 | style::Color, 29 | text::Line, 30 | widgets::{Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState}, 31 | Frame, Terminal, 32 | }; 33 | use std::{ 34 | cell::{Cell, RefCell}, 35 | collections::VecDeque, 36 | io::stdout, 37 | rc::Rc, 38 | }; 39 | use tokio::{select, sync::Notify, task::LocalSet}; 40 | use tui_textarea::{CursorMove, Input, Key, TextArea}; 41 | 42 | #[derive(Default)] 43 | pub struct Tui { 44 | on_redraw: Notify, 45 | on_input: Notify, 46 | logs: RefCell>>, 47 | input_queue: RefCell>, 48 | text_area: RefCell>, 49 | main_list: RefCell>>, 50 | main_scroll: Cell, 51 | main_scroll_state: RefCell, 52 | } 53 | 54 | impl Tui { 55 | fn request_redraw(&self) { self.on_redraw.notify_one() } 56 | fn log(&self, msg: String, color: u32) { 57 | let color = match color { 58 | 0xFFFFFF => Color::Reset, 59 | 0xFFA500 => Color::LightYellow, 60 | 0x55ABEC => Color::LightBlue, 61 | 0xF2B2CC => Color::LightRed, 62 | 0xFF4FFF => Color::LightMagenta, 63 | 0x00FF00 => Color::Green, 64 | 0xFF0000 => Color::Red, 65 | _ => unreachable!(), 66 | }; 67 | self.logs.borrow_mut().push_back(Line::styled(msg, color)); 68 | self.request_redraw() 69 | } 70 | 71 | fn set_main_list(&self, list: Vec>) { 72 | *self.main_list.borrow_mut() = list; 73 | self.set_main_scroll(|x| x) 74 | } 75 | 76 | fn set_main_scroll(&self, upd: impl FnOnce(u16) -> u16) { 77 | let list = self.main_list.borrow(); 78 | let i = upd(self.main_scroll.get()); 79 | self.main_scroll.set(i.min(list.len().max(1) as u16 - 1)); 80 | let mut state = self.main_scroll_state.borrow_mut(); 81 | *state = state.position(i as _).content_length(list.len()) 82 | } 83 | 84 | fn frame(&self, frame: &mut Frame) { 85 | let layout = Layout::vertical([Constraint::Fill(1), Constraint::Length(1)]).split(frame.area()); 86 | frame.render_widget(&*self.text_area.borrow(), layout[1]); 87 | 88 | let log_size; 89 | let main_list = self.main_list.borrow(); 90 | if main_list.is_empty() { 91 | log_size = layout[0] 92 | } else { 93 | let layout = Layout::horizontal([Constraint::Percentage(50), Constraint::Fill(1)]).split(layout[0]); 94 | log_size = layout[0]; 95 | let main_list_size = layout[1]; 96 | frame.render_widget(Paragraph::new(main_list.clone()).scroll((self.main_scroll.get(), 0)), main_list_size); 97 | let scroll = Scrollbar::new(ScrollbarOrientation::VerticalRight); 98 | frame.render_stateful_widget( 99 | scroll, 100 | main_list_size.inner(Margin { horizontal: 1, vertical: 0 }), 101 | &mut *self.main_scroll_state.borrow_mut(), 102 | ) 103 | } 104 | 105 | let mut log_buffer = self.logs.borrow_mut(); 106 | while log_buffer.len() > log_size.height as _ { 107 | log_buffer.pop_front(); 108 | } 109 | frame.render_widget(Paragraph::new(Vec::from_iter(log_buffer.iter().cloned())), log_size) 110 | } 111 | } 112 | 113 | #[tokio::main(flavor = "current_thread")] 114 | async fn main() { 115 | let tasks = LocalSet::new(); 116 | tasks.spawn_local(async { 117 | enable_raw_mode().unwrap(); 118 | stdout().execute(EnterAlternateScreen).unwrap(); 119 | let mut evts = EventStream::new(); 120 | let mut term = Terminal::new(CrosstermBackend::new(std::io::stderr())).unwrap(); 121 | let tui = Rc::::default(); 122 | let _factory = build_factory(tui.clone()); 123 | loop { 124 | term.draw(|frame| tui.frame(frame)).unwrap(); 125 | let evt = select! { 126 | () = tui.on_redraw.notified() => None, 127 | evt = evts.next() => if let Some(Ok(x)) = evt { Some(x) } else { break } 128 | }; 129 | if let Some(Event::Key(evt)) = evt { 130 | let evt = Input::from(evt); 131 | if evt.ctrl && (evt.key == Key::Char('c') || evt.key == Key::Char('d')) { 132 | break; 133 | } else if evt.ctrl && evt.key == Key::Char('l') { 134 | tui.logs.borrow_mut().clear() 135 | } else if evt.key == Key::PageUp { 136 | tui.set_main_scroll(|x| x.saturating_sub(8)) 137 | } else if evt.key == Key::PageDown { 138 | tui.set_main_scroll(|x| x.saturating_add(8)) 139 | } else if evt.ctrl && evt.key == Key::Char('m') || evt.key == Key::Enter { 140 | let mut text_area = tui.text_area.borrow_mut(); 141 | tui.input_queue.borrow_mut().extend(text_area.lines().get(text_area.cursor().0).cloned()); 142 | text_area.move_cursor(CursorMove::End); 143 | text_area.insert_newline() 144 | } else { 145 | tui.text_area.borrow_mut().input(evt); 146 | } 147 | tui.on_input.notify_waiters() 148 | } 149 | } 150 | disable_raw_mode().unwrap(); 151 | stdout().execute(LeaveAlternateScreen).unwrap(); 152 | }); 153 | tasks.await 154 | } 155 | -------------------------------------------------------------------------------- /server/RustImpl/src/process/blocking_fluid_output.rs: -------------------------------------------------------------------------------- 1 | use super::{IntoProcess, Process}; 2 | use crate::access::{EachTank, TankAccess}; 3 | use crate::action::{ActionFuture, Call}; 4 | use crate::factory::{read_tanks, tanks_to_fluid_map, Factory}; 5 | use crate::recipe::FluidOutput; 6 | use crate::util::{alive, join_tasks, spawn}; 7 | use abort_on_drop::ChildTask; 8 | use flexstr::{local_str, LocalStr}; 9 | use std::{ 10 | cell::RefCell, 11 | rc::{Rc, Weak}, 12 | }; 13 | 14 | pub struct BlockingFluidOutputConfig { 15 | pub accesses: Vec, 16 | pub outputs: Vec, 17 | } 18 | 19 | pub struct BlockingFluidOutputProcess { 20 | weak: Weak>, 21 | config: BlockingFluidOutputConfig, 22 | factory: Weak>, 23 | } 24 | 25 | impl IntoProcess for BlockingFluidOutputConfig { 26 | type Output = BlockingFluidOutputProcess; 27 | fn into_process(self, factory: &Factory) -> Rc> { 28 | Rc::new_cyclic(|weak| { 29 | RefCell::new(Self::Output { weak: weak.clone(), config: self, factory: factory.weak.clone() }) 30 | }) 31 | } 32 | } 33 | 34 | impl Process for BlockingFluidOutputProcess { 35 | fn run(&self, factory: &Factory) -> ChildTask> { 36 | let mut enough = true; 37 | for output in &self.config.outputs { 38 | if factory.search_n_fluid(&output.fluid) < output.n_wanted { 39 | enough = false; 40 | break; 41 | } 42 | } 43 | if enough { 44 | return spawn(async { Ok(()) }); 45 | } 46 | let tanks = read_tanks(&*factory.borrow_server(), &self.config.accesses, |access| EachTank { 47 | addr: access.buses[0].addr.clone(), 48 | side: access.buses[0].tank_side, 49 | }); 50 | let weak = self.weak.clone(); 51 | spawn(async move { 52 | let tanks = tanks.await?; 53 | let mut tasks = Vec::new(); 54 | { 55 | alive!(weak, this); 56 | upgrade_mut!(this.factory, factory); 57 | let tanks = tanks_to_fluid_map(&tanks); 58 | for output in &this.config.outputs { 59 | let Some(&(_, slot)) = tanks.get(&output.fluid) else { continue }; 60 | let n_stored = factory.search_n_fluid(&output.fluid); 61 | let qty = output.n_wanted - n_stored; 62 | if qty > 0 { 63 | let weak = weak.clone(); 64 | tasks.push(spawn(async move { 65 | let bus = { 66 | alive!(weak, this); 67 | upgrade_mut!(this.factory, factory); 68 | factory.fluid_bus_allocate() 69 | }; 70 | let bus = bus.await?; 71 | let task; 72 | { 73 | alive!(weak, this); 74 | upgrade!(this.factory, factory); 75 | let server = factory.borrow_server(); 76 | let access = server.load_balance(&this.config.accesses).1; 77 | let bus_of_tank = &access.buses[bus]; 78 | task = ActionFuture::from(Call { 79 | addr: bus_of_tank.addr.clone(), 80 | func: local_str!("transferFluid"), 81 | args: vec![ 82 | bus_of_tank.tank_side.into(), 83 | bus_of_tank.bus_side.into(), 84 | qty.into(), 85 | (slot + 1).into(), 86 | ], 87 | }); 88 | server.enqueue_request_group(&access.client, vec![task.clone().into()]) 89 | } 90 | let result = task.await.map(|_| ()); 91 | alive(&weak)?.borrow().factory.upgrade().unwrap().borrow_mut().fluid_bus_deposit([bus]); 92 | result 93 | })); 94 | } 95 | } 96 | } 97 | join_tasks(tasks).await 98 | }) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /server/RustImpl/src/process/blocking_output.rs: -------------------------------------------------------------------------------- 1 | use super::{extract_output, list_inv, IntoProcess, Inventory, Process, SlotFilter}; 2 | use crate::access::InvAccess; 3 | use crate::factory::Factory; 4 | use crate::item::Item; 5 | use crate::recipe::Output; 6 | use crate::util::{alive, join_tasks, spawn}; 7 | use abort_on_drop::ChildTask; 8 | use flexstr::LocalStr; 9 | use fnv::FnvHashMap; 10 | use std::{ 11 | cell::RefCell, 12 | cmp::{max, min}, 13 | collections::hash_map::Entry, 14 | rc::{Rc, Weak}, 15 | }; 16 | 17 | pub struct BlockingOutputConfig { 18 | pub accesses: Vec, 19 | pub slot_filter: Option, 20 | pub outputs: Vec, 21 | } 22 | 23 | pub struct BlockingOutputProcess { 24 | weak: Weak>, 25 | config: BlockingOutputConfig, 26 | factory: Weak>, 27 | } 28 | 29 | impl_inventory!(BlockingOutputProcess); 30 | 31 | impl IntoProcess for BlockingOutputConfig { 32 | type Output = BlockingOutputProcess; 33 | fn into_process(self, factory: &Factory) -> Rc> { 34 | Rc::new_cyclic(|weak| { 35 | RefCell::new(Self::Output { weak: weak.clone(), config: self, factory: factory.weak.clone() }) 36 | }) 37 | } 38 | } 39 | 40 | struct Info { 41 | n_stored: i32, 42 | n_wanted: i32, 43 | } 44 | 45 | impl Process for BlockingOutputProcess { 46 | fn run(&self, factory: &Factory) -> ChildTask> { 47 | let mut enough = true; 48 | for output in &self.config.outputs { 49 | if factory.search_n_stored(&output.item) < output.n_wanted { 50 | enough = false; 51 | break; 52 | } 53 | } 54 | if enough { 55 | return spawn(async { Ok(()) }); 56 | } 57 | let stacks = list_inv(self, factory); 58 | let weak = self.weak.clone(); 59 | spawn(async move { 60 | let stacks = stacks.await?; 61 | let mut tasks = Vec::new(); 62 | { 63 | alive!(weak, this); 64 | upgrade_mut!(this.factory, factory); 65 | let mut infos = FnvHashMap::<&Rc, Info>::default(); 66 | for (slot, stack) in stacks.iter().enumerate() { 67 | if let Some(ref slot_filter) = this.config.slot_filter { 68 | if !slot_filter(slot) { 69 | continue; 70 | } 71 | } 72 | if let Some(stack) = stack { 73 | let info = match infos.entry(&stack.item) { 74 | Entry::Occupied(entry) => entry.into_mut(), 75 | Entry::Vacant(entry) => { 76 | let mut info = Info { n_wanted: 0, n_stored: factory.get_n_stored(&stack.item) }; 77 | for output in &this.config.outputs { 78 | if output.item.apply(&stack.item) { 79 | info.n_wanted = max(info.n_wanted, output.n_wanted) 80 | } 81 | } 82 | entry.insert(info) 83 | } 84 | }; 85 | let to_extract = min(info.n_wanted - info.n_stored, stack.size); 86 | if to_extract <= 0 { 87 | continue; 88 | } 89 | info.n_stored += to_extract; 90 | tasks.push(extract_output(this, factory, slot, to_extract)) 91 | } 92 | } 93 | } 94 | join_tasks(tasks).await 95 | }) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /server/RustImpl/src/process/buffered.rs: -------------------------------------------------------------------------------- 1 | use super::super::access::InvAccess; 2 | use super::super::action::{ActionFuture, Call}; 3 | use super::super::factory::Factory; 4 | use super::super::item::{insert_into_inventory, jammer, Filter, InsertPlan, Item, ItemStack}; 5 | use super::super::recipe::{compute_demands, resolve_inputs, Demand, Input, Outputs, Recipe}; 6 | use super::super::util::{alive, join_outputs, join_tasks, spawn}; 7 | use super::{extract_output, list_inv, scattering_insert, ExtractFilter, IntoProcess, Inventory, Process, SlotFilter}; 8 | use abort_on_drop::ChildTask; 9 | use flexstr::{local_str, LocalStr}; 10 | use fnv::FnvHashMap; 11 | use std::{ 12 | cell::RefCell, 13 | rc::{Rc, Weak}, 14 | }; 15 | 16 | impl_input!(BufferedInput); 17 | #[derive(Clone)] 18 | pub struct BufferedInput { 19 | item: Filter, 20 | size: i32, 21 | allow_backup: bool, 22 | extra_backup: i32, 23 | } 24 | 25 | impl BufferedInput { 26 | pub fn new(item: Filter, size: i32) -> Self { BufferedInput { item, size, allow_backup: false, extra_backup: 0 } } 27 | } 28 | 29 | impl_recipe!(BufferedRecipe, BufferedInput); 30 | #[derive(Clone)] 31 | pub struct BufferedRecipe { 32 | pub outputs: Rc, 33 | pub inputs: Vec, 34 | pub max_inputs: i32, 35 | } 36 | 37 | pub struct BufferedConfig { 38 | pub name: LocalStr, 39 | pub accesses: Vec, 40 | pub slot_filter: Option, 41 | pub to_extract: Option, 42 | pub recipes: Vec, 43 | pub max_recipe_inputs: i32, 44 | pub stocks: Vec, 45 | } 46 | 47 | pub struct BufferedProcess { 48 | weak: Weak>, 49 | config: BufferedConfig, 50 | factory: Weak>, 51 | } 52 | 53 | impl_inventory!(BufferedProcess); 54 | 55 | impl IntoProcess for BufferedConfig { 56 | type Output = BufferedProcess; 57 | fn into_process(self, factory: &Factory) -> Rc> { 58 | Rc::new_cyclic(|weak| { 59 | RefCell::new(Self::Output { weak: weak.clone(), config: self, factory: factory.weak.clone() }) 60 | }) 61 | } 62 | } 63 | 64 | impl Process for BufferedProcess { 65 | fn run(&self, factory: &Factory) -> ChildTask> { 66 | if self.config.to_extract.is_none() && self.config.stocks.is_empty() { 67 | if compute_demands(factory, &self.config.recipes).is_empty() { 68 | return spawn(async { Ok(()) }); 69 | } 70 | } 71 | let stacks = list_inv(self, factory); 72 | let weak = self.weak.clone(); 73 | spawn(async move { 74 | let mut stacks = stacks.await?; 75 | let mut tasks = Vec::new(); 76 | { 77 | alive!(weak, this); 78 | upgrade_mut!(this.factory, factory); 79 | let mut remaining_size = this.config.max_recipe_inputs; 80 | let mut existing_size = FnvHashMap::, i32>::default(); 81 | 'slot: for (slot, stack) in stacks.iter_mut().enumerate() { 82 | if let Some(ref to_extract) = this.config.to_extract { 83 | if let Some(some_stack) = stack { 84 | if to_extract(factory, slot, some_stack) { 85 | tasks.push(extract_output(this, factory, slot, some_stack.item.max_size)); 86 | *stack = Some(ItemStack { item: jammer(), size: 1 }); 87 | continue 'slot; 88 | } 89 | } 90 | } 91 | if let Some(ref slot_filter) = this.config.slot_filter { 92 | if !slot_filter(slot) { 93 | *stack = Some(ItemStack { item: jammer(), size: 1 }); 94 | continue 'slot; 95 | } 96 | } 97 | if let Some(stack) = stack { 98 | *existing_size.entry(stack.item.clone()).or_default() += stack.size; 99 | for stock in &this.config.stocks { 100 | if stock.item.apply(&stack.item) { 101 | continue 'slot; 102 | } 103 | } 104 | remaining_size -= stack.size; 105 | } 106 | } 107 | for stock in &this.config.stocks { 108 | if let Some((item, info)) = factory.search_item(&stock.item) { 109 | let existing = existing_size.entry(item.clone()).or_default(); 110 | let to_insert = (stock.size - *existing) 111 | .min(info.borrow().get_availability(stock.allow_backup, stock.extra_backup)); 112 | if to_insert <= 0 { 113 | continue; 114 | } 115 | let InsertPlan { n_inserted, insertions } = insert_into_inventory(&mut stacks, item, to_insert); 116 | if n_inserted <= 0 { 117 | continue; 118 | } 119 | *existing += n_inserted; 120 | let reservation = factory.reserve_item(&this.config.name, item, n_inserted); 121 | tasks.push(scattering_insert(this, factory, reservation, insertions)) 122 | } 123 | } 124 | if remaining_size > 0 { 125 | 'recipe: for Demand { i_recipe, .. } in compute_demands(factory, &this.config.recipes) { 126 | let recipe = &this.config.recipes[i_recipe]; 127 | if let Some(mut inputs) = resolve_inputs(factory, recipe) { 128 | let size_per_set: i32 = recipe.inputs.iter().map(|x| x.size).sum(); 129 | inputs.n_sets = inputs.n_sets.min(remaining_size / size_per_set); 130 | if inputs.n_sets <= 0 { 131 | continue 'recipe; 132 | } 133 | let existing_total: i32 = 134 | inputs.items.iter().map(|item| *existing_size.entry(item.clone()).or_default()).sum(); 135 | inputs.n_sets = inputs.n_sets.min((recipe.max_inputs - existing_total) / size_per_set); 136 | if inputs.n_sets <= 0 { 137 | continue 'recipe; 138 | } 139 | let backup = stacks.clone(); 140 | let mut plans = Vec::new(); 141 | plans.reserve(recipe.inputs.len()); 142 | 'retry: loop { 143 | for (i_input, item) in inputs.items.iter().enumerate() { 144 | let to_insert = inputs.n_sets * recipe.inputs[i_input].size; 145 | let plan = insert_into_inventory(&mut stacks, item, to_insert); 146 | if plan.n_inserted == to_insert { 147 | plans.push(plan) 148 | } else { 149 | inputs.n_sets -= 1; 150 | if inputs.n_sets <= 0 { 151 | continue 'recipe; 152 | } 153 | plans.clear(); 154 | stacks = backup.clone(); 155 | continue 'retry; 156 | } 157 | } 158 | break 'retry; 159 | } 160 | for (i_input, item) in inputs.items.iter().enumerate() { 161 | *existing_size.get_mut(item).unwrap() += plans[i_input].n_inserted 162 | } 163 | remaining_size -= inputs.n_sets * size_per_set; 164 | tasks.push(this.execute_recipe(factory, inputs.items, plans)); 165 | if remaining_size <= 0 { 166 | break 'recipe; 167 | } 168 | } 169 | } 170 | } 171 | } 172 | join_tasks(tasks).await 173 | }) 174 | } 175 | } 176 | 177 | impl BufferedProcess { 178 | fn execute_recipe( 179 | &self, 180 | factory: &mut Factory, 181 | items: Vec>, 182 | plans: Vec, 183 | ) -> ChildTask> { 184 | let mut bus_slots = Vec::new(); 185 | let slots_to_free = Rc::new(RefCell::new(Vec::new())); 186 | for (i_input, item) in items.into_iter().enumerate() { 187 | let reservation = factory.reserve_item(&self.config.name, &item, plans[i_input].n_inserted); 188 | let bus_slot = factory.bus_allocate(); 189 | let slots_to_free = slots_to_free.clone(); 190 | let weak = self.factory.clone(); 191 | bus_slots.push(spawn(async move { 192 | let bus_slot = bus_slot.await?; 193 | slots_to_free.borrow_mut().push(bus_slot); 194 | let extraction = reservation.extract(&*alive(&weak)?.borrow(), bus_slot); 195 | extraction.await.map(|_| bus_slot) 196 | })) 197 | } 198 | let weak = self.weak.clone(); 199 | spawn(async move { 200 | let bus_slots = join_outputs(bus_slots).await; 201 | let slots_to_free = Rc::into_inner(slots_to_free).unwrap().into_inner(); 202 | let task = async { 203 | let bus_slots = bus_slots?; 204 | let mut tasks = Vec::new(); 205 | { 206 | alive!(weak, this); 207 | upgrade!(this.factory, factory); 208 | let server = factory.borrow_server(); 209 | let access = server.load_balance(this.config.accesses.iter()).1; 210 | let mut group = Vec::new(); 211 | for (i_input, InsertPlan { insertions, .. }) in plans.into_iter().enumerate() { 212 | for (inv_slot, size) in insertions { 213 | let action = ActionFuture::from(Call { 214 | addr: access.addr.clone(), 215 | func: local_str!("transferItem"), 216 | args: vec![ 217 | access.bus_side.into(), 218 | access.inv_side.into(), 219 | size.into(), 220 | (bus_slots[i_input] + 1).into(), 221 | (inv_slot + 1).into(), 222 | ], 223 | }); 224 | group.push(action.clone().into()); 225 | tasks.push(spawn(async move { action.await.map(|_| ()) })) 226 | } 227 | } 228 | server.enqueue_request_group(&access.client, group) 229 | } 230 | join_tasks(tasks).await?; 231 | alive!(weak, this); 232 | upgrade_mut!(this.factory, factory); 233 | for slot_to_free in &slots_to_free { 234 | factory.bus_free(*slot_to_free) 235 | } 236 | Ok(()) 237 | }; 238 | let result = task.await; 239 | if result.is_err() { 240 | alive!(weak, this); 241 | upgrade_mut!(this.factory, factory); 242 | factory.bus_deposit(slots_to_free); 243 | } 244 | result 245 | }) 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /server/RustImpl/src/process/manual_ui.rs: -------------------------------------------------------------------------------- 1 | use super::{list_inv, scattering_insert, IntoProcess, Inventory, Process}; 2 | use crate::access::InvAccess; 3 | use crate::item::{insert_into_inventory, InsertPlan, ItemStack}; 4 | use crate::util::{alive, join_tasks, spawn}; 5 | use crate::{factory::Factory, Tui}; 6 | use abort_on_drop::ChildTask; 7 | use flexstr::LocalStr; 8 | use futures_util::future::OptionFuture; 9 | use ratatui::style::{Color, Modifier, Style}; 10 | use ratatui::text::{Line, Span}; 11 | use regex::Regex; 12 | use std::{ 13 | cell::RefCell, 14 | rc::{Rc, Weak}, 15 | }; 16 | 17 | pub struct ManualUiConfig { 18 | pub accesses: Vec, 19 | } 20 | 21 | impl_inventory!(ManualUiProcess); 22 | pub struct ManualUiProcess { 23 | weak: Weak>, 24 | config: ManualUiConfig, 25 | factory: Weak>, 26 | latest_view: Vec, 27 | _input_handler: ChildTask<()>, 28 | } 29 | 30 | impl IntoProcess for ManualUiConfig { 31 | type Output = ManualUiProcess; 32 | fn into_process(self, factory: &Factory) -> Rc> { 33 | Rc::new_cyclic(|weak| { 34 | let tui = factory.config.tui.clone(); 35 | let weak = weak.clone(); 36 | RefCell::new(Self::Output { 37 | weak: weak.clone(), 38 | config: self, 39 | factory: factory.weak.clone(), 40 | latest_view: Vec::new(), 41 | _input_handler: spawn(async move { input_handler(tui, weak).await }), 42 | }) 43 | }) 44 | } 45 | } 46 | 47 | async fn input_handler(tui: Rc, weak: Weak>) { 48 | loop { 49 | tui.on_input.notified().await; 50 | let Some(this) = weak.upgrade() else { break }; 51 | let this = this.borrow(); 52 | this.update_view(&this.factory.upgrade().unwrap().borrow().config.tui); 53 | } 54 | } 55 | 56 | fn make_pred(needle: &str) -> Box bool> { 57 | if needle.is_empty() { 58 | Box::new(|_| true) 59 | } else if needle.starts_with("=") { 60 | let Ok(regex) = Regex::new(&needle[1..]) else { return Box::new(|_| false) }; 61 | Box::new(move |x| regex.is_match(&x.item.name)) 62 | } else { 63 | let Ok(regex) = Regex::new(needle) else { return Box::new(|_| false) }; 64 | Box::new(move |x| regex.is_match(&x.item.label)) 65 | } 66 | } 67 | 68 | impl ManualUiProcess { 69 | fn update_view(&self, tui: &Tui) { 70 | let text_area = tui.text_area.borrow(); 71 | let mut needle = text_area.lines().get(text_area.cursor().0).map(|x| x.as_str()).unwrap_or(""); 72 | if let Some(pos) = needle.rfind('*') { 73 | needle = &needle[..pos] 74 | } 75 | let pred = make_pred(needle); 76 | tui.set_main_list( 77 | (self.latest_view.iter().filter(|x| pred(x))) 78 | .map(|x| { 79 | Line::from(vec![ 80 | Span::raw(format!("{} * ", x.size)), 81 | Span::styled(format!("{} ", x.item.label), Color::LightGreen), 82 | Span::styled(x.item.name.to_std_string(), Style::from(Color::Gray).add_modifier(Modifier::DIM)), 83 | ]) 84 | }) 85 | .collect(), 86 | ); 87 | tui.request_redraw() 88 | } 89 | } 90 | 91 | impl Process for ManualUiProcess { 92 | fn run(&self, factory: &Factory) -> ChildTask> { 93 | let stacks = (!self.config.accesses.is_empty()).then(|| list_inv(self, factory)); 94 | let weak = self.weak.clone(); 95 | spawn(async move { 96 | let mut stacks = OptionFuture::from(stacks).await.transpose()?.unwrap_or_default(); 97 | let mut tasks = Vec::new(); 98 | { 99 | alive_mut!(weak, this); 100 | upgrade_mut!(this.factory, factory); 101 | this.latest_view = Vec::from_iter(factory.items.iter().map(|(item, info)| { 102 | let info = info.borrow(); 103 | ItemStack { item: item.clone(), size: info.n_stored } 104 | })); 105 | this.latest_view.sort_by_key(|x| -x.size); 106 | let tui = factory.config.tui.clone(); 107 | this.update_view(&tui); 108 | for request in tui.input_queue.borrow_mut().drain(..) { 109 | let Some(pos) = request.rfind('*') else { continue }; 110 | let pred = make_pred(&request[..pos]); 111 | let Some(stack) = this.latest_view.iter().find(|x| pred(x)) else { continue }; 112 | let Ok(mut size) = request[pos + 1..].parse() else { continue }; 113 | size = factory.items.get(&stack.item).map_or(0, |info| info.borrow().n_stored).min(size); 114 | loop { 115 | let InsertPlan { n_inserted, insertions } = 116 | insert_into_inventory(&mut stacks, &stack.item, size.min(stack.item.max_size)); 117 | if n_inserted <= 0 { 118 | break; 119 | }; 120 | let reservation = factory.reserve_item("manual", &stack.item, n_inserted); 121 | tasks.push(scattering_insert(this, factory, reservation, insertions)); 122 | size -= n_inserted 123 | } 124 | } 125 | } 126 | join_tasks(tasks).await 127 | }) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /server/RustImpl/src/process/mod.rs: -------------------------------------------------------------------------------- 1 | use super::access::InvAccess; 2 | use super::action::{ActionFuture, Call, List}; 3 | use super::factory::{Factory, Reservation}; 4 | use super::item::ItemStack; 5 | use super::util::{alive, join_tasks, spawn}; 6 | use abort_on_drop::ChildTask; 7 | use flexstr::{local_str, LocalStr}; 8 | use std::{ 9 | cell::RefCell, 10 | iter::once, 11 | rc::{Rc, Weak}, 12 | }; 13 | 14 | pub trait Process: 'static { 15 | fn run(&self, factory: &Factory) -> ChildTask>; 16 | } 17 | 18 | pub trait IntoProcess { 19 | type Output: Process; 20 | fn into_process(self, factory: &Factory) -> Rc>; 21 | } 22 | 23 | impl IntoProcess for T { 24 | type Output = T; 25 | fn into_process(self, _: &Factory) -> Rc> { Rc::new(RefCell::new(self)) } 26 | } 27 | 28 | pub type SlotFilter = Box bool>; 29 | pub type ExtractFilter = Box bool>; 30 | pub fn extract_all() -> Option { Some(Box::new(|_, _, _| true)) } 31 | 32 | pub trait Inventory: 'static { 33 | fn get_accesses(&self) -> &Vec; 34 | fn get_weak(&self) -> &Weak>; 35 | fn get_factory(&self) -> &Weak>; 36 | } 37 | 38 | macro_rules! impl_inventory { 39 | ($i:ident) => { 40 | impl Inventory for $i { 41 | fn get_accesses(&self) -> &Vec { &self.config.accesses } 42 | fn get_weak(&self) -> &Weak> { &self.weak } 43 | fn get_factory(&self) -> &Weak> { &self.factory } 44 | } 45 | }; 46 | } 47 | 48 | fn list_inv(this: &T, factory: &Factory) -> ActionFuture 49 | where 50 | T: Inventory, 51 | { 52 | let server = factory.borrow_server(); 53 | let access = server.load_balance(this.get_accesses()).1; 54 | let action = ActionFuture::from(List { addr: access.addr.clone(), side: access.inv_side }); 55 | server.enqueue_request_group(&access.client, vec![action.clone().into()]); 56 | action 57 | } 58 | 59 | fn extract_output(this: &T, factory: &mut Factory, slot: usize, size: i32) -> ChildTask> 60 | where 61 | T: Inventory, 62 | { 63 | let bus_slot = factory.bus_allocate(); 64 | let weak = this.get_weak().clone(); 65 | spawn(async move { 66 | let bus_slot = bus_slot.await?; 67 | let action; 68 | { 69 | alive!(weak, this); 70 | alive!(this.get_factory(), factory); 71 | let server = factory.borrow_server(); 72 | let access = server.load_balance(this.get_accesses()).1; 73 | action = ActionFuture::from(Call { 74 | addr: access.addr.clone(), 75 | func: local_str!("transferItem"), 76 | args: vec![ 77 | access.inv_side.into(), 78 | access.bus_side.into(), 79 | size.into(), 80 | (slot + 1).into(), 81 | (bus_slot + 1).into(), 82 | ], 83 | }); 84 | server.enqueue_request_group(&access.client, vec![action.clone().into()]) 85 | } 86 | let result = action.await.map(|_| ()); 87 | alive!(weak, this); 88 | upgrade_mut!(this.get_factory(), factory); 89 | factory.bus_deposit(once(bus_slot)); 90 | result 91 | }) 92 | } 93 | 94 | fn scattering_insert( 95 | this: &T, 96 | factory: &mut Factory, 97 | reservation: Reservation, 98 | insertions: U, 99 | ) -> ChildTask> 100 | where 101 | T: Inventory, 102 | U: IntoIterator + 'static, 103 | { 104 | let bus_slot = factory.bus_allocate(); 105 | let weak = this.get_weak().clone(); 106 | spawn(async move { 107 | let bus_slot = bus_slot.await?; 108 | let task = async { 109 | let extraction = { 110 | alive!(weak, this); 111 | upgrade!(this.get_factory(), factory); 112 | reservation.extract(factory, bus_slot) 113 | }; 114 | extraction.await?; 115 | let mut tasks = Vec::new(); 116 | { 117 | alive!(weak, this); 118 | upgrade!(this.get_factory(), factory); 119 | let server = factory.borrow_server(); 120 | for (inv_slot, size) in insertions.into_iter() { 121 | let access = server.load_balance(this.get_accesses()).1; 122 | let action = ActionFuture::from(Call { 123 | addr: access.addr.clone(), 124 | func: local_str!("transferItem"), 125 | args: vec![ 126 | access.bus_side.into(), 127 | access.inv_side.into(), 128 | size.into(), 129 | (bus_slot + 1).into(), 130 | (inv_slot + 1).into(), 131 | ], 132 | }); 133 | server.enqueue_request_group(&access.client, vec![action.clone().into()]); 134 | tasks.push(spawn(async move { action.await.map(|_| ()) })) 135 | } 136 | } 137 | join_tasks(tasks).await?; 138 | alive!(weak, this); 139 | upgrade_mut!(this.get_factory(), factory); 140 | factory.bus_free(bus_slot); 141 | Ok(()) 142 | }; 143 | let result = task.await; 144 | if result.is_err() { 145 | alive!(weak, this); 146 | upgrade_mut!(this.get_factory(), factory); 147 | factory.bus_deposit(once(bus_slot)) 148 | } 149 | result 150 | }) 151 | } 152 | 153 | mod blocking_fluid_output; 154 | mod blocking_output; 155 | mod buffered; 156 | mod crafting_grid; 157 | mod fluid_slotted; 158 | mod manual_ui; 159 | mod misc; 160 | mod multi_inv_slotted; 161 | mod reactor; 162 | mod redstone; 163 | mod scattering; 164 | mod slotted; 165 | pub use blocking_fluid_output::*; 166 | pub use blocking_output::*; 167 | pub use buffered::*; 168 | pub use crafting_grid::*; 169 | pub use fluid_slotted::*; 170 | pub use manual_ui::*; 171 | pub use misc::*; 172 | pub use multi_inv_slotted::*; 173 | pub use reactor::*; 174 | pub use redstone::*; 175 | pub use scattering::*; 176 | pub use slotted::*; 177 | -------------------------------------------------------------------------------- /server/RustImpl/src/process/reactor.rs: -------------------------------------------------------------------------------- 1 | use super::super::access::ComponentAccess; 2 | use super::super::action::{ActionFuture, Call, Print}; 3 | use super::super::factory::Factory; 4 | use super::super::item::Filter; 5 | use super::super::lua_value::call_result; 6 | use super::super::util::{alive, join_outputs, spawn}; 7 | use super::{IntoProcess, Process}; 8 | use abort_on_drop::ChildTask; 9 | use flexstr::{local_fmt, local_str, LocalStr}; 10 | use std::{ 11 | cell::RefCell, 12 | future::Future, 13 | rc::{Rc, Weak}, 14 | }; 15 | use tokio::time::Instant; 16 | 17 | trait ReactorProcess { 18 | fn get_accesses(&self) -> &Vec; 19 | fn n_cyanite_wanted(&self) -> i32; 20 | fn has_turbine(&self) -> bool; 21 | } 22 | 23 | macro_rules! impl_reactor_process { 24 | ($i:ident) => { 25 | impl ReactorProcess for $i { 26 | fn get_accesses(&self) -> &Vec { &self.config.accesses } 27 | fn n_cyanite_wanted(&self) -> i32 { self.config.n_cyanite_wanted } 28 | fn has_turbine(&self) -> bool { self.config.has_turbine } 29 | } 30 | }; 31 | } 32 | 33 | fn run_reactor(this: &T, factory: &Factory, run: F) -> ChildTask> 34 | where 35 | T: ReactorProcess, 36 | U: Future>, 37 | F: FnOnce(f64) -> U + 'static, 38 | { 39 | if this.n_cyanite_wanted() > 0 40 | && factory.search_n_stored(&Filter::Label(local_str!("Cyanite Ingot"))) < this.n_cyanite_wanted() 41 | { 42 | return spawn(async move { run(0.0).await }); 43 | } 44 | let server = factory.borrow_server(); 45 | if this.has_turbine() { 46 | let states = ["getHotFluidAmount", "getHotFluidAmountMax"] 47 | .into_iter() 48 | .map(|x| { 49 | let access = server.load_balance(this.get_accesses()).1; 50 | let action = ActionFuture::from(Call { 51 | addr: access.addr.clone(), 52 | func: LocalStr::from_static(x), 53 | args: Vec::new(), 54 | }); 55 | server.enqueue_request_group(&access.client, vec![action.clone().into()]); 56 | spawn(async move { call_result::(action.await?) }) 57 | }) 58 | .collect(); 59 | spawn(async move { run(join_outputs(states).await.map(|states| states[0] / states[1])?).await }) 60 | } else { 61 | let access = server.load_balance(this.get_accesses()).1; 62 | let action = ActionFuture::from(Call { 63 | addr: access.addr.clone(), 64 | func: local_str!("getEnergyStored"), 65 | args: Vec::new(), 66 | }); 67 | server.enqueue_request_group(&access.client, vec![action.clone().into()]); 68 | spawn(async move { run(action.await.and_then(call_result).map(|x: f64| x / 1E7)?).await }) 69 | } 70 | } 71 | 72 | pub struct HysteresisReactorConfig { 73 | pub name: LocalStr, 74 | pub accesses: Vec, 75 | pub n_cyanite_wanted: i32, 76 | pub has_turbine: bool, 77 | pub lower_bound: f64, // typical: 0.3 78 | pub upper_bound: f64, // typical: 0.7 79 | } 80 | 81 | pub struct HysteresisReactorProcess { 82 | weak: Weak>, 83 | config: HysteresisReactorConfig, 84 | factory: Weak>, 85 | prev_on: Option, 86 | } 87 | 88 | impl_reactor_process!(HysteresisReactorProcess); 89 | 90 | impl IntoProcess for HysteresisReactorConfig { 91 | type Output = HysteresisReactorProcess; 92 | fn into_process(self, factory: &Factory) -> Rc> { 93 | Rc::new_cyclic(|weak| { 94 | RefCell::new(Self::Output { 95 | weak: weak.clone(), 96 | config: self, 97 | factory: factory.weak.clone(), 98 | prev_on: None, 99 | }) 100 | }) 101 | } 102 | } 103 | 104 | impl Process for HysteresisReactorProcess { 105 | fn run(&self, factory: &Factory) -> ChildTask> { 106 | let weak = self.weak.clone(); 107 | run_reactor(self, factory, |pv| async move { 108 | let action; 109 | let on; 110 | { 111 | alive!(weak, this); 112 | on = if pv < this.config.lower_bound { 113 | true 114 | } else if pv > this.config.upper_bound { 115 | false 116 | } else { 117 | return Ok(()); 118 | }; 119 | if this.prev_on == Some(on) { 120 | return Ok(()); 121 | } 122 | upgrade!(this.factory, factory); 123 | factory.log(Print { 124 | text: local_fmt!("{}: {}", this.config.name, if on { "on" } else { "off" }), 125 | color: 0xFF4FFF, 126 | beep: None, 127 | }); 128 | let server = factory.borrow_server(); 129 | let access = server.load_balance(&this.config.accesses).1; 130 | action = ActionFuture::from(Call { 131 | addr: access.addr.clone(), 132 | func: local_str!("setActive"), 133 | args: vec![on.into()], 134 | }); 135 | server.enqueue_request_group(&access.client, vec![action.clone().into()]); 136 | }; 137 | action.await?; 138 | alive(&weak)?.borrow_mut().prev_on = Some(on); 139 | Ok(()) 140 | }) 141 | } 142 | } 143 | 144 | pub struct ProportionalReactorConfig { 145 | pub name: LocalStr, 146 | pub accesses: Vec, 147 | pub n_cyanite_wanted: i32, 148 | pub has_turbine: bool, 149 | } 150 | 151 | pub struct ProportionalReactorProcess { 152 | weak: Weak>, 153 | config: ProportionalReactorConfig, 154 | factory: Weak>, 155 | prev_rod: Option, 156 | } 157 | 158 | impl_reactor_process!(ProportionalReactorProcess); 159 | 160 | impl IntoProcess for ProportionalReactorConfig { 161 | type Output = ProportionalReactorProcess; 162 | fn into_process(self, factory: &Factory) -> Rc> { 163 | Rc::new_cyclic(|weak| { 164 | RefCell::new(Self::Output { 165 | weak: weak.clone(), 166 | config: self, 167 | factory: factory.weak.clone(), 168 | prev_rod: None, 169 | }) 170 | }) 171 | } 172 | } 173 | 174 | fn to_percent(x: f64) -> i16 { (x * 100.0).round() as _ } 175 | 176 | impl Process for ProportionalReactorProcess { 177 | fn run(&self, factory: &Factory) -> ChildTask> { 178 | let weak = self.weak.clone(); 179 | run_reactor(self, factory, |pv| async move { 180 | let rod = to_percent(pv); 181 | let action; 182 | { 183 | alive!(weak, this); 184 | upgrade!(this.factory, factory); 185 | factory.log(Print { text: local_fmt!("{}: {}%", this.config.name, rod), color: 0xFF4FFF, beep: None }); 186 | if this.prev_rod == Some(rod) { 187 | return Ok(()); 188 | } 189 | let server = factory.borrow_server(); 190 | let access = server.load_balance(&this.config.accesses).1; 191 | action = ActionFuture::from(Call { 192 | addr: access.addr.clone(), 193 | func: local_str!("setAllControlRodLevels"), 194 | args: vec![rod.into()], 195 | }); 196 | server.enqueue_request_group(&access.client, vec![action.clone().into()]); 197 | }; 198 | action.await?; 199 | alive(&weak)?.borrow_mut().prev_rod = Some(rod); 200 | Ok(()) 201 | }) 202 | } 203 | } 204 | 205 | pub struct PIDReactorConfig { 206 | pub name: LocalStr, 207 | pub accesses: Vec, 208 | pub n_cyanite_wanted: i32, 209 | pub has_turbine: bool, 210 | pub k_p: f64, // typical: 1.00 211 | pub k_i: f64, // typical: 0.01 212 | pub k_d: f64, // typical: 0.00 213 | } 214 | 215 | struct PIDReactorState { 216 | prev_t: Instant, 217 | prev_e: f64, 218 | accum: f64, 219 | } 220 | 221 | pub struct PIDReactorProcess { 222 | weak: Weak>, 223 | config: PIDReactorConfig, 224 | factory: Weak>, 225 | state: Option, 226 | prev_rod: Option, 227 | } 228 | 229 | impl_reactor_process!(PIDReactorProcess); 230 | 231 | impl IntoProcess for PIDReactorConfig { 232 | type Output = PIDReactorProcess; 233 | fn into_process(self, factory: &Factory) -> Rc> { 234 | Rc::new_cyclic(|weak| { 235 | RefCell::new(Self::Output { 236 | weak: weak.clone(), 237 | config: self, 238 | factory: factory.weak.clone(), 239 | state: None, 240 | prev_rod: None, 241 | }) 242 | }) 243 | } 244 | } 245 | 246 | impl Process for PIDReactorProcess { 247 | fn run(&self, factory: &Factory) -> ChildTask> { 248 | let weak = self.weak.clone(); 249 | run_reactor(self, factory, |pv| async move { 250 | let rod; 251 | let action; 252 | { 253 | alive_mut!(weak, this); 254 | let t = Instant::now(); 255 | let e = (0.5 - pv) * 2.0; 256 | let mut accum = 0.0; 257 | let mut diff = 0.0; 258 | if let Some(ref state) = this.state { 259 | let dt = (t - state.prev_t).as_secs_f64(); 260 | accum = (state.accum + dt * e * this.config.k_i).clamp(-1.0, 1.0); 261 | diff = (e - state.prev_e) / dt 262 | } 263 | this.state = Some(PIDReactorState { prev_t: t, prev_e: e, accum }); 264 | let op = e * this.config.k_p + accum + diff * this.config.k_d; 265 | rod = to_percent((0.5 - op).clamp(0.0, 1.0)); 266 | upgrade!(this.factory, factory); 267 | factory.log(Print { 268 | text: local_fmt!( 269 | "{}: E={}%, I={}%, O={}%", 270 | this.config.name, 271 | to_percent(-e), 272 | to_percent(accum), 273 | 100 - rod 274 | ), 275 | color: 0xFF4FFF, 276 | beep: None, 277 | }); 278 | if this.prev_rod == Some(rod) { 279 | return Ok(()); 280 | } 281 | let server = factory.borrow_server(); 282 | let access = server.load_balance(&this.config.accesses).1; 283 | action = ActionFuture::from(Call { 284 | addr: access.addr.clone(), 285 | func: local_str!("setAllControlRodLevels"), 286 | args: vec![rod.into()], 287 | }); 288 | server.enqueue_request_group(&access.client, vec![action.clone().into()]); 289 | }; 290 | action.await?; 291 | alive(&weak)?.borrow_mut().prev_rod = Some(rod); 292 | Ok(()) 293 | }) 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /server/RustImpl/src/process/redstone.rs: -------------------------------------------------------------------------------- 1 | use super::super::access::SidedAccess; 2 | use super::super::action::{ActionFuture, Call, Print}; 3 | use super::super::factory::Factory; 4 | use super::super::lua_value::call_result; 5 | use super::super::recipe::Outputs; 6 | use super::super::util::{alive, spawn}; 7 | use super::{IntoProcess, Process}; 8 | use abort_on_drop::ChildTask; 9 | use flexstr::{local_fmt, local_str, LocalStr}; 10 | use std::{ 11 | cell::RefCell, 12 | rc::{Rc, Weak}, 13 | }; 14 | 15 | pub type RedstoneOutput = Box i32>; 16 | pub fn emit_when_want_item(name: LocalStr, off: i32, on: i32, outputs: Box) -> RedstoneOutput { 17 | Box::new(move |factory| { 18 | if outputs.get_priority(&factory).is_some() { 19 | factory.log(Print { text: local_fmt!("{}: on", name), color: 0xFF4FFF, beep: None }); 20 | return on; 21 | } 22 | off 23 | }) 24 | } 25 | 26 | pub struct RedstoneEmitterConfig { 27 | pub accesses: Vec, 28 | pub output: RedstoneOutput, 29 | } 30 | 31 | pub struct RedstoneEmitterProcess { 32 | weak: Weak>, 33 | config: RedstoneEmitterConfig, 34 | prev_value: Option, 35 | } 36 | 37 | impl IntoProcess for RedstoneEmitterConfig { 38 | type Output = RedstoneEmitterProcess; 39 | fn into_process(self, _factory: &Factory) -> Rc> { 40 | Rc::new_cyclic(|weak| RefCell::new(Self::Output { weak: weak.clone(), config: self, prev_value: None })) 41 | } 42 | } 43 | 44 | impl Process for RedstoneEmitterProcess { 45 | fn run(&self, factory: &Factory) -> ChildTask> { 46 | let value = (self.config.output)(factory); 47 | if Some(value) == self.prev_value { 48 | spawn(async { Ok(()) }) 49 | } else { 50 | let server = factory.borrow_server(); 51 | let access = server.load_balance(&self.config.accesses).1; 52 | let action = ActionFuture::from(Call { 53 | addr: access.addr.clone(), 54 | func: local_str!("setOutput"), 55 | args: vec![access.side.into(), value.into()], 56 | }); 57 | server.enqueue_request_group(&access.client, vec![action.clone().into()]); 58 | let weak = self.weak.clone(); 59 | spawn(async move { 60 | action.await?; 61 | alive_mut!(weak, this); 62 | this.prev_value = Some(value); 63 | Ok(()) 64 | }) 65 | } 66 | } 67 | } 68 | 69 | pub struct RedstoneConditionalConfig { 70 | pub name: Option, 71 | pub accesses: Vec, 72 | pub condition: Box bool>, 73 | pub child: T, 74 | } 75 | 76 | pub struct RedstoneConditionalProcess { 77 | weak: Weak>>, 78 | factory: Weak>, 79 | name: Option, 80 | accesses: Vec, 81 | condition: Box bool>, 82 | child: Rc>, 83 | } 84 | 85 | impl IntoProcess for RedstoneConditionalConfig { 86 | type Output = RedstoneConditionalProcess; 87 | fn into_process(self, factory: &Factory) -> Rc> { 88 | Rc::new_cyclic(|weak| { 89 | RefCell::new(Self::Output { 90 | weak: weak.clone(), 91 | factory: factory.weak.clone(), 92 | name: self.name, 93 | accesses: self.accesses, 94 | condition: self.condition, 95 | child: self.child.into_process(factory), 96 | }) 97 | }) 98 | } 99 | } 100 | 101 | impl Process for RedstoneConditionalProcess { 102 | fn run(&self, factory: &Factory) -> ChildTask> { 103 | let server = factory.borrow_server(); 104 | let access = server.load_balance(&self.accesses).1; 105 | let action = ActionFuture::from(Call { 106 | addr: access.addr.clone(), 107 | func: local_str!("getInput"), 108 | args: vec![access.side.into()], 109 | }); 110 | server.enqueue_request_group(&access.client, vec![action.clone().into()]); 111 | let weak = self.weak.clone(); 112 | spawn(async move { 113 | let value = call_result(action.await?)?; 114 | let task = { 115 | alive!(weak, this); 116 | upgrade!(this.factory, factory); 117 | if (this.condition)(value) { 118 | this.child.borrow().run(factory) 119 | } else { 120 | if let Some(name) = &this.name { 121 | factory.log(Print { text: local_fmt!("{}: skipped", name), color: 0xFF4FFF, beep: None }) 122 | } 123 | return Ok(()); 124 | } 125 | }; 126 | task.await.unwrap() 127 | }) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /server/RustImpl/src/process/scattering.rs: -------------------------------------------------------------------------------- 1 | use super::super::access::InvAccess; 2 | use super::super::factory::Factory; 3 | use super::super::item::{Filter, ItemStack}; 4 | use super::super::recipe::{compute_demands, resolve_inputs, Demand, Input, Outputs, Recipe}; 5 | use super::super::util::{alive, join_tasks, spawn}; 6 | use super::{extract_output, list_inv, scattering_insert, ExtractFilter, IntoProcess, Inventory, Process}; 7 | use abort_on_drop::ChildTask; 8 | use flexstr::{local_fmt, LocalStr}; 9 | use fnv::FnvHashMap; 10 | use std::{ 11 | cell::RefCell, 12 | rc::{Rc, Weak}, 13 | }; 14 | 15 | impl_input!(ScatteringInput); 16 | #[derive(Clone)] 17 | pub struct ScatteringInput { 18 | item: Filter, 19 | size: i32, 20 | allow_backup: bool, 21 | extra_backup: i32, 22 | } 23 | 24 | impl ScatteringInput { 25 | pub fn new(item: Filter) -> Self { ScatteringInput { item, size: 1, allow_backup: false, extra_backup: 0 } } 26 | } 27 | 28 | impl_recipe!(ScatteringRecipe, ScatteringInput); 29 | #[derive(Clone)] 30 | pub struct ScatteringRecipe { 31 | outputs: Rc, 32 | inputs: Vec, 33 | } 34 | 35 | impl ScatteringRecipe { 36 | pub fn new(outputs: Rc, input: ScatteringInput) -> Self { 37 | ScatteringRecipe { outputs, inputs: vec![input] } 38 | } 39 | } 40 | 41 | pub struct ScatteringConfig { 42 | pub name: LocalStr, 43 | pub accesses: Vec, 44 | // plant_sower: 6, 7, .., 14 45 | pub input_slots: Vec, 46 | pub to_extract: Option, 47 | pub recipes: Vec, 48 | pub max_per_slot: i32, 49 | } 50 | 51 | impl_inventory!(ScatteringProcess); 52 | pub struct ScatteringProcess { 53 | weak: Weak>, 54 | config: ScatteringConfig, 55 | factory: Weak>, 56 | } 57 | 58 | impl IntoProcess for ScatteringConfig { 59 | type Output = ScatteringProcess; 60 | fn into_process(self, factory: &Factory) -> Rc> { 61 | Rc::new_cyclic(|weak| { 62 | RefCell::new(Self::Output { weak: weak.clone(), config: self, factory: factory.weak.clone() }) 63 | }) 64 | } 65 | } 66 | 67 | impl Process for ScatteringProcess { 68 | fn run(&self, factory: &Factory) -> ChildTask> { 69 | if self.config.to_extract.is_none() && compute_demands(factory, &self.config.recipes).is_empty() { 70 | return spawn(async { Ok(()) }); 71 | } 72 | let stacks = list_inv(self, factory); 73 | let weak = self.weak.clone(); 74 | spawn(async move { 75 | let mut stacks = stacks.await?; 76 | let mut tasks = Vec::new(); 77 | { 78 | alive!(weak, this); 79 | upgrade_mut!(this.factory, factory); 80 | let mut is_input_slot = vec![false; stacks.len()]; 81 | for slot in &this.config.input_slots { 82 | if *slot >= stacks.len() { 83 | return Err(local_fmt!("{}: invalid slot", this.config.name)); 84 | } 85 | is_input_slot[*slot] = true 86 | } 87 | if let Some(ref to_extract) = this.config.to_extract { 88 | for (slot, stack) in stacks.iter().enumerate() { 89 | if let Some(stack) = stack { 90 | if !is_input_slot[slot] && to_extract(factory, slot, stack) { 91 | tasks.push(extract_output(this, factory, slot, stack.item.max_size)) 92 | } 93 | } 94 | } 95 | } 96 | for Demand { i_recipe, .. } in compute_demands(factory, &this.config.recipes) { 97 | if let Some(mut inputs) = resolve_inputs(factory, &this.config.recipes[i_recipe]) { 98 | let mut insertions = FnvHashMap::::default(); 99 | let mut n_inserted = 0; 100 | while inputs.n_sets > 0 { 101 | let mut best = None; 102 | for slot in &this.config.input_slots { 103 | if let Some(ref stack) = stacks[*slot] { 104 | if stack.item == inputs.items[0] { 105 | if let Some((_, best_size)) = best { 106 | if stack.size >= best_size { 107 | continue; 108 | } 109 | } 110 | best = Some((*slot, stack.size)) 111 | } 112 | } else { 113 | best = Some((*slot, 0)); 114 | break; 115 | } 116 | } 117 | let Some((slot, size)) = best else { break }; 118 | if size >= this.config.max_per_slot.min(inputs.items[0].max_size) { 119 | break; 120 | } 121 | inputs.n_sets -= 1; 122 | n_inserted += 1; 123 | *insertions.entry(slot).or_default() += 1; 124 | let stack = &mut stacks[slot]; 125 | if let Some(ref mut stack) = stack { 126 | stack.size += 1 127 | } else { 128 | *stack = Some(ItemStack { item: inputs.items[0].clone(), size: 1 }) 129 | } 130 | } 131 | if n_inserted > 0 { 132 | let reservation = factory.reserve_item(&this.config.name, &inputs.items[0], n_inserted); 133 | tasks.push(scattering_insert(this, factory, reservation, insertions)) 134 | } 135 | } 136 | } 137 | } 138 | join_tasks(tasks).await 139 | }) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /server/RustImpl/src/process/slotted.rs: -------------------------------------------------------------------------------- 1 | use super::super::access::InvAccess; 2 | use super::super::action::{ActionFuture, Call}; 3 | use super::super::factory::Factory; 4 | use super::super::item::{Filter, ItemStack}; 5 | use super::super::recipe::{compute_demands, Demand, Input, Outputs, Recipe}; 6 | use super::super::util::{alive, join_outputs, join_tasks, spawn}; 7 | use super::{extract_output, list_inv, ExtractFilter, IntoProcess, Inventory, Process}; 8 | use abort_on_drop::ChildTask; 9 | use flexstr::{local_str, LocalStr}; 10 | use fnv::{FnvHashMap, FnvHashSet}; 11 | use std::{ 12 | cell::RefCell, 13 | rc::{Rc, Weak}, 14 | }; 15 | 16 | #[derive(Clone)] 17 | pub struct SlottedInput { 18 | item: Filter, 19 | size: i32, 20 | slots: Vec<(usize, i32)>, 21 | allow_backup: bool, 22 | extra_backup: i32, 23 | } 24 | 25 | impl_input!(SlottedInput); 26 | impl SlottedInput { 27 | pub fn new(item: Filter, slots: Vec<(usize, i32)>) -> Self { 28 | let size = slots.iter().map(|(_, size)| size).sum(); 29 | SlottedInput { item, size, slots, allow_backup: false, extra_backup: 0 } 30 | } 31 | } 32 | 33 | impl_recipe!(SlottedRecipe, SlottedInput); 34 | #[derive(Clone)] 35 | pub struct SlottedRecipe { 36 | pub outputs: Rc, 37 | pub inputs: Vec, 38 | pub max_sets: i32, 39 | } 40 | 41 | pub struct SlottedConfig { 42 | pub name: LocalStr, 43 | pub accesses: Vec, 44 | pub input_slots: Vec, 45 | pub to_extract: Option, 46 | pub recipes: Vec, 47 | pub strict_priority: bool, 48 | } 49 | 50 | pub struct SlottedProcess { 51 | weak: Weak>, 52 | config: SlottedConfig, 53 | factory: Weak>, 54 | } 55 | 56 | impl_inventory!(SlottedProcess); 57 | 58 | impl IntoProcess for SlottedConfig { 59 | type Output = SlottedProcess; 60 | fn into_process(self, factory: &Factory) -> Rc> { 61 | Rc::new_cyclic(|weak| { 62 | RefCell::new(Self::Output { weak: weak.clone(), config: self, factory: factory.weak.clone() }) 63 | }) 64 | } 65 | } 66 | 67 | impl Process for SlottedProcess { 68 | fn run(&self, factory: &Factory) -> ChildTask> { 69 | if self.config.to_extract.is_none() && compute_demands(factory, &self.config.recipes).is_empty() { 70 | return spawn(async { Ok(()) }); 71 | } 72 | let stacks = list_inv(self, factory); 73 | let weak = self.weak.clone(); 74 | spawn(async move { 75 | let stacks = stacks.await?; 76 | let mut tasks = Vec::new(); 77 | { 78 | alive!(weak, this); 79 | upgrade_mut!(this.factory, factory); 80 | let mut existing_inputs = FnvHashMap::>::default(); 81 | for slot in &this.config.input_slots { 82 | existing_inputs.insert(*slot, None); 83 | } 84 | for (slot, stack) in stacks.into_iter().enumerate() { 85 | if let Some(stack) = stack { 86 | if let Some(existing_input) = existing_inputs.get_mut(&slot) { 87 | *existing_input = Some(stack) 88 | } else if let Some(ref to_extract) = this.config.to_extract { 89 | if to_extract(factory, slot, &stack) { 90 | tasks.push(extract_output(this, factory, slot, stack.item.max_size)) 91 | } 92 | } 93 | } 94 | } 95 | let mut demands = compute_demands(factory, &this.config.recipes); 96 | if this.config.strict_priority { 97 | demands.truncate(1) 98 | } 99 | 'recipe: for mut demand in demands.into_iter() { 100 | let recipe = &this.config.recipes[demand.i_recipe]; 101 | let mut used_slots = FnvHashSet::::default(); 102 | for (i_input, input) in recipe.inputs.iter().enumerate() { 103 | for (slot, mult) in &input.slots { 104 | let existing_input = existing_inputs.get(slot).unwrap(); 105 | let existing_size = if let Some(existing_input) = existing_input { 106 | if existing_input.item != demand.inputs.items[i_input] { 107 | continue 'recipe; 108 | } 109 | existing_input.size 110 | } else { 111 | 0 112 | }; 113 | demand.inputs.n_sets = demand.inputs.n_sets.min( 114 | ((recipe.max_sets * mult).min(demand.inputs.items[i_input].max_size) - existing_size) 115 | / mult, 116 | ); 117 | if demand.inputs.n_sets <= 0 { 118 | continue 'recipe; 119 | } 120 | used_slots.insert(*slot); 121 | } 122 | } 123 | for (slot, existing_input) in &existing_inputs { 124 | if existing_input.is_some() && !used_slots.contains(slot) { 125 | continue 'recipe; 126 | } 127 | } 128 | tasks.push(this.execute_recipe(factory, demand)); 129 | break; 130 | } 131 | } 132 | join_tasks(tasks).await 133 | }) 134 | } 135 | } 136 | 137 | impl SlottedProcess { 138 | fn execute_recipe(&self, factory: &mut Factory, demand: Demand) -> ChildTask> { 139 | let mut bus_slots = Vec::new(); 140 | let slots_to_free = Rc::new(RefCell::new(Vec::new())); 141 | let recipe = &self.config.recipes[demand.i_recipe]; 142 | for (i_input, input) in recipe.inputs.iter().enumerate() { 143 | let reservation = factory.reserve_item( 144 | &self.config.name, 145 | &demand.inputs.items[i_input], 146 | demand.inputs.n_sets * input.size, 147 | ); 148 | let bus_slot = factory.bus_allocate(); 149 | let slots_to_free = slots_to_free.clone(); 150 | let weak = self.factory.clone(); 151 | bus_slots.push(spawn(async move { 152 | let bus_slot = bus_slot.await?; 153 | slots_to_free.borrow_mut().push(bus_slot); 154 | let extraction = reservation.extract(&*alive(&weak)?.borrow(), bus_slot); 155 | extraction.await.map(|_| bus_slot) 156 | })) 157 | } 158 | let weak = self.weak.clone(); 159 | spawn(async move { 160 | let bus_slots = join_outputs(bus_slots).await; 161 | let slots_to_free = Rc::into_inner(slots_to_free).unwrap().into_inner(); 162 | let task = async { 163 | let bus_slots = bus_slots?; 164 | let mut tasks = Vec::new(); 165 | { 166 | alive!(weak, this); 167 | upgrade!(this.factory, factory); 168 | let server = factory.borrow_server(); 169 | let access = server.load_balance(&this.config.accesses).1; 170 | let mut group = Vec::new(); 171 | let recipe = &this.config.recipes[demand.i_recipe]; 172 | for (i_input, input) in recipe.inputs.iter().enumerate() { 173 | for (inv_slot, mult) in &input.slots { 174 | let action = ActionFuture::from(Call { 175 | addr: access.addr.clone(), 176 | func: local_str!("transferItem"), 177 | args: vec![ 178 | access.bus_side.into(), 179 | access.inv_side.into(), 180 | (demand.inputs.n_sets * mult).into(), 181 | (bus_slots[i_input] + 1).into(), 182 | (inv_slot + 1).into(), 183 | ], 184 | }); 185 | group.push(action.clone().into()); 186 | tasks.push(spawn(async move { action.await.map(|_| ()) })); 187 | } 188 | } 189 | server.enqueue_request_group(&access.client, group) 190 | } 191 | join_tasks(tasks).await?; 192 | alive!(weak, this); 193 | upgrade_mut!(this.factory, factory); 194 | for slot_to_free in &slots_to_free { 195 | factory.bus_free(*slot_to_free) 196 | } 197 | Ok(()) 198 | }; 199 | let result = task.await; 200 | if result.is_err() { 201 | alive!(weak, this); 202 | upgrade_mut!(this.factory, factory); 203 | factory.bus_deposit(slots_to_free); 204 | } 205 | result 206 | }) 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /server/RustImpl/src/recipe.rs: -------------------------------------------------------------------------------- 1 | use super::item::{Filter, Item}; 2 | use crate::factory::Factory; 3 | use flexstr::LocalStr; 4 | use fnv::FnvHashMap; 5 | use std::{ 6 | cmp::{max_by, min_by}, 7 | collections::hash_map::Entry, 8 | rc::Rc, 9 | }; 10 | 11 | pub trait Outputs { 12 | fn get_priority(&self, factory: &Factory) -> Option; 13 | } 14 | 15 | impl Option> Outputs for T { 16 | fn get_priority(&self, factory: &Factory) -> Option { self(factory) } 17 | } 18 | 19 | pub trait BoxedOutputs { 20 | fn and(self, other: Self) -> Self; 21 | fn or(self, other: Self) -> Self; 22 | fn not(self) -> Self; 23 | fn map_priority(self, f: impl Fn(&Factory, f64) -> f64 + 'static) -> Self; 24 | } 25 | 26 | impl BoxedOutputs for Rc { 27 | fn and(self, other: Self) -> Self { 28 | Rc::new(move |factory: &_| { 29 | max_by(self.get_priority(factory), other.get_priority(factory), |x, y| x.partial_cmp(y).unwrap()) 30 | }) 31 | } 32 | 33 | fn or(self, other: Self) -> Self { 34 | Rc::new(move |factory: &_| { 35 | min_by(self.get_priority(factory), other.get_priority(factory), |x, y| x.partial_cmp(y).unwrap()) 36 | }) 37 | } 38 | 39 | fn not(self) -> Self { 40 | Rc::new(move |factory: &_| match self.get_priority(factory) { 41 | Some(_) => None, 42 | None => Some(1.), 43 | }) 44 | } 45 | 46 | fn map_priority(self, f: impl Fn(&Factory, f64) -> f64 + 'static) -> Self { 47 | Rc::new(move |factory: &_| self.get_priority(factory).map(|x| f(factory, x))) 48 | } 49 | } 50 | 51 | pub fn ignore_outputs(priority: f64) -> Rc { Rc::new(move |_: &_| Some(priority)) } 52 | 53 | pub struct Output { 54 | pub item: Filter, 55 | pub n_wanted: i32, 56 | } 57 | 58 | impl Output { 59 | pub fn new(item: Filter, n_wanted: i32) -> Rc { Rc::new(Self { item, n_wanted }) } 60 | } 61 | 62 | impl Outputs for Output { 63 | fn get_priority(&self, factory: &Factory) -> Option { 64 | let n_stored = factory.search_n_stored(&self.item); 65 | let n_needed = self.n_wanted - n_stored; 66 | if n_needed > 0 { 67 | Some(n_needed as f64 / self.n_wanted as f64) 68 | } else { 69 | None 70 | } 71 | } 72 | } 73 | 74 | pub struct FluidOutput { 75 | pub fluid: LocalStr, 76 | pub n_wanted: i64, 77 | } 78 | 79 | impl FluidOutput { 80 | pub fn new(fluid: LocalStr, n_wanted: i64) -> Rc { Rc::new(Self { fluid, n_wanted }) } 81 | } 82 | 83 | impl Outputs for FluidOutput { 84 | fn get_priority(&self, factory: &Factory) -> Option { 85 | let n_stored = factory.search_n_fluid(&self.fluid); 86 | let n_needed = self.n_wanted - n_stored; 87 | if n_needed > 0 { 88 | Some(n_needed as f64 / self.n_wanted as f64) 89 | } else { 90 | None 91 | } 92 | } 93 | } 94 | 95 | pub trait Input { 96 | fn get_item(&self) -> &Filter; 97 | fn get_size(&self) -> i32; 98 | fn get_allow_backup(&self) -> bool; 99 | fn get_extra_backup(&self) -> i32; 100 | fn allow_backup(self) -> Self; 101 | fn extra_backup(self, size: i32) -> Self; 102 | } 103 | 104 | macro_rules! impl_input { 105 | ($i:ident) => { 106 | impl Input for $i { 107 | fn get_item(&self) -> &Filter { &self.item } 108 | fn get_size(&self) -> i32 { self.size } 109 | fn get_allow_backup(&self) -> bool { self.allow_backup } 110 | fn get_extra_backup(&self) -> i32 { self.extra_backup } 111 | 112 | fn allow_backup(mut self) -> Self { 113 | self.allow_backup = true; 114 | self 115 | } 116 | 117 | fn extra_backup(mut self, size: i32) -> Self { 118 | self.extra_backup += size; 119 | self 120 | } 121 | } 122 | }; 123 | } 124 | 125 | pub trait Recipe: Clone { 126 | type In: Input; 127 | fn get_outputs(&self) -> &dyn Outputs; 128 | fn get_inputs(&self) -> &Vec; 129 | } 130 | 131 | macro_rules! impl_recipe { 132 | ($r:ident, $i:ident) => { 133 | impl Recipe for $r { 134 | type In = $i; 135 | fn get_outputs(&self) -> &dyn Outputs { &*self.outputs } 136 | fn get_inputs(&self) -> &Vec<$i> { &self.inputs } 137 | } 138 | }; 139 | } 140 | 141 | pub struct ResolvedInputs { 142 | pub n_sets: i32, 143 | pub priority: i32, 144 | pub items: Vec>, 145 | } 146 | 147 | struct InputInfo { 148 | n_available: i32, 149 | n_needed: i32, 150 | } 151 | 152 | pub fn resolve_inputs(factory: &Factory, recipe: &impl Recipe) -> Option { 153 | let mut items = Vec::new(); 154 | items.reserve(recipe.get_inputs().len()); 155 | let mut infos = FnvHashMap::<&Rc, InputInfo>::default(); 156 | let mut max_size_bound = i32::MAX; 157 | for input in recipe.get_inputs() { 158 | if let Some((item, item_info)) = factory.search_item(input.get_item()) { 159 | items.push(item.clone()); 160 | match infos.entry(item) { 161 | Entry::Occupied(input_info) => input_info.into_mut().n_needed += input.get_size(), 162 | Entry::Vacant(input_info) => { 163 | input_info.insert(InputInfo { 164 | // Note: backup params are considered for only the first input of the same item. 165 | n_available: (item_info.borrow()) 166 | .get_availability(input.get_allow_backup(), input.get_extra_backup()), 167 | n_needed: input.get_size(), 168 | }); 169 | } 170 | } 171 | max_size_bound = max_size_bound.min(item.max_size / input.get_size()); 172 | } else { 173 | return None; 174 | } 175 | } 176 | let mut availability_bound = i32::MAX; 177 | for (_, input_info) in infos.into_iter() { 178 | let limit = input_info.n_available / input_info.n_needed; 179 | availability_bound = availability_bound.min(limit) 180 | } 181 | let n_sets = max_size_bound.min(availability_bound); 182 | if n_sets > 0 { 183 | Some(ResolvedInputs { n_sets, priority: availability_bound, items }) 184 | } else { 185 | None 186 | } 187 | } 188 | 189 | pub struct Demand { 190 | pub i_recipe: usize, 191 | pub inputs: ResolvedInputs, 192 | pub priority: f64, 193 | } 194 | 195 | pub fn compute_demands(factory: &Factory, recipes: &[impl Recipe]) -> Vec { 196 | let mut result = Vec::new(); 197 | for (i_recipe, recipe) in recipes.iter().enumerate() { 198 | let Some(mut priority) = recipe.get_outputs().get_priority(factory) else { continue }; 199 | let Some(inputs) = resolve_inputs(factory, recipe) else { continue }; 200 | priority *= inputs.priority as f64; 201 | result.push(Demand { i_recipe, inputs, priority }) 202 | } 203 | result.sort_by(|x: &Demand, y: &Demand| x.priority.partial_cmp(&y.priority).unwrap().reverse()); 204 | result 205 | } 206 | -------------------------------------------------------------------------------- /server/RustImpl/src/server.rs: -------------------------------------------------------------------------------- 1 | use crate::access::Access; 2 | use crate::action::ActionRequest; 3 | use crate::lua_value::{serialize, vec_to_table, Parser, Value}; 4 | use crate::util::spawn; 5 | use crate::Tui; 6 | use abort_on_drop::ChildTask; 7 | use flexstr::{local_fmt, LocalStr}; 8 | use fnv::FnvHashMap; 9 | use socket2::{Domain, SockAddr, Socket, Type}; 10 | use std::{ 11 | cell::RefCell, 12 | collections::VecDeque, 13 | fmt::Write, 14 | mem::replace, 15 | net::{Ipv6Addr, SocketAddr}, 16 | rc::{Rc, Weak}, 17 | time::Duration, 18 | }; 19 | use tokio::{ 20 | io::{AsyncReadExt, AsyncWriteExt}, 21 | net::{tcp::OwnedReadHalf, tcp::OwnedWriteHalf, TcpListener}, 22 | time::sleep, 23 | }; 24 | 25 | pub struct Server { 26 | pub tui: Rc, 27 | clients: Option>>, 28 | logins: FnvHashMap>>, 29 | _acceptor: ChildTask<()>, 30 | } 31 | 32 | impl Drop for Server { 33 | fn drop(&mut self) { 34 | while let Some(client) = self.clients.take() { 35 | self.clients = Rc::into_inner(client).unwrap().into_inner().next.take() 36 | } 37 | } 38 | } 39 | 40 | enum WriterState { 41 | NotWriting(OwnedWriteHalf), 42 | Writing { _task: ChildTask<()> }, 43 | Invalid, 44 | } 45 | 46 | struct Client { 47 | weak: Weak>, 48 | tui: Rc, 49 | log_prefix: String, 50 | next: Option>>, 51 | prev: Option>>, 52 | server: Weak>, 53 | login: Option, 54 | _reader: ChildTask<()>, 55 | request_queue: VecDeque>>>, 56 | request_queue_size: usize, 57 | response_queue: VecDeque>>, 58 | writer: WriterState, 59 | timeout: Option>, 60 | } 61 | 62 | impl Drop for Client { 63 | fn drop(&mut self) { 64 | self.log(format_args!("disconnected")); 65 | let message: LocalStr = [&self.log_prefix, " disconnected"].into_iter().collect(); 66 | for x in self.request_queue.iter().flatten().chain(&self.response_queue) { 67 | x.borrow_mut().on_fail(message.clone()) 68 | } 69 | } 70 | } 71 | 72 | impl Client { 73 | fn log(&self, args: std::fmt::Arguments) { self.tui.log(format!("{}: {args}", self.log_prefix), 0xFFFFFF) } 74 | fn disconnect(&mut self) { self.disconnect_by_server(&mut self.server.upgrade().unwrap().borrow_mut()); } 75 | fn disconnect_by_server(&mut self, server: &mut Server) { 76 | if let Some(login) = &self.login { 77 | server.logins.remove(login); 78 | } 79 | if let Some(next) = self.next.as_ref() { 80 | next.borrow_mut().prev = self.prev.clone() 81 | } 82 | if let Some(prev) = self.prev.as_ref() { 83 | prev.upgrade().unwrap().borrow_mut().next = self.next.take() 84 | } else { 85 | server.clients = self.next.take() 86 | } 87 | } 88 | 89 | fn log_and_disconnect(&mut self, args: std::fmt::Arguments) { 90 | self.log(args); 91 | self.disconnect() 92 | } 93 | 94 | fn enqueue_request_group(&mut self, group: Vec>>) { 95 | self.request_queue_size += group.len(); 96 | self.request_queue.push_back(group); 97 | let writer = replace(&mut self.writer, WriterState::Invalid); 98 | if let WriterState::NotWriting(stream) = writer { 99 | self.update_timeout(false); 100 | self.writer = WriterState::Writing { _task: spawn(writer_main(self.weak.clone(), stream)) } 101 | } else { 102 | self.writer = writer 103 | } 104 | } 105 | 106 | fn update_timeout(&mut self, restart: bool) { 107 | if self.request_queue_size == 0 && self.response_queue.is_empty() { 108 | self.timeout = None 109 | } else if restart || self.timeout.is_none() { 110 | self.timeout = Some(spawn(timeout_main(self.weak.clone()))) 111 | } 112 | } 113 | 114 | fn estimate_load(&self) -> usize { self.request_queue_size + self.response_queue.len() } 115 | } 116 | 117 | async fn timeout_main(client: Weak>) { 118 | sleep(Duration::from_secs(30)).await; 119 | if let Some(this) = client.upgrade() { 120 | this.borrow_mut().log_and_disconnect(format_args!("request timeout")) 121 | } 122 | } 123 | 124 | async fn writer_main(client: Weak>, mut stream: OwnedWriteHalf) { 125 | loop { 126 | let mut data = Vec::new(); 127 | { 128 | let Some(this) = client.upgrade() else { break }; 129 | let mut this = this.borrow_mut(); 130 | let Some(group) = this.request_queue.pop_front() else { 131 | break this.writer = WriterState::NotWriting(stream); 132 | }; 133 | this.request_queue_size -= group.len(); 134 | let mut value = Vec::new(); 135 | for x in group { 136 | value.push(x.borrow_mut().build_request()); 137 | this.response_queue.push_back(x) 138 | } 139 | serialize(&vec_to_table(value).into(), &mut data); 140 | #[cfg(feature = "dump_traffic")] 141 | this.log(format_args!("out: {}", data.iter().map(|x| char::from(*x)).collect::())); 142 | } 143 | if let Err(e) = stream.write_all(&data).await { 144 | if let Some(this) = client.upgrade() { 145 | this.borrow_mut().log_and_disconnect(format_args!("error writing: {}", e)) 146 | } 147 | break; 148 | } 149 | } 150 | } 151 | 152 | fn on_packet(client: &Rc>, value: Value) -> Result<(), LocalStr> { 153 | let mut this = client.borrow_mut(); 154 | if this.login.is_some() { 155 | if let Some(x) = this.response_queue.pop_front() { 156 | this.update_timeout(true); 157 | x.borrow_mut().on_response(value) 158 | } else { 159 | Err(local_fmt!("unexpected packet: {:?}", value)) 160 | } 161 | } else if let Value::S(login) = value { 162 | upgrade_mut!(this.server, server); 163 | write!(this.log_prefix, "[{}]", login).unwrap(); 164 | this.log(format_args!("logged in")); 165 | this.login = Some(login.clone()); 166 | drop(this); 167 | server.login(login, Rc::downgrade(client)); 168 | Ok(()) 169 | } else { 170 | Err(local_fmt!("invalid login packet: {:?}", value)) 171 | } 172 | } 173 | 174 | async fn reader_main(client: Weak>, mut stream: OwnedReadHalf) { 175 | let mut data = [0; 4096]; 176 | let mut parser = Parser::new(); 177 | loop { 178 | let n_read = stream.read(&mut data).await; 179 | let Some(this) = client.upgrade() else { break }; 180 | match n_read { 181 | Err(e) => break this.borrow_mut().log_and_disconnect(format_args!("error reading: {}", e)), 182 | Ok(n_read) => { 183 | if n_read > 0 { 184 | let data = &data[..n_read]; 185 | #[cfg(feature = "dump_traffic")] 186 | this.borrow().log(format_args!("in: {}", data.iter().map(|x| char::from(*x)).collect::())); 187 | if let Err(e) = parser.shift(data, &mut |x| on_packet(&this, x)) { 188 | this.borrow_mut().log_and_disconnect(format_args!("error decoding packet: {}", e)); 189 | break; 190 | } 191 | } else { 192 | this.borrow_mut().log_and_disconnect(format_args!("client disconnected")); 193 | break; 194 | } 195 | } 196 | } 197 | } 198 | } 199 | 200 | fn create_listener(port: u16) -> TcpListener { 201 | let socket = Socket::new(Domain::IPV6, Type::STREAM, None).unwrap(); 202 | socket.set_reuse_address(true).unwrap(); 203 | socket.set_only_v6(false).unwrap(); 204 | socket.bind(&SockAddr::from(SocketAddr::from((Ipv6Addr::UNSPECIFIED, port)))).unwrap(); 205 | socket.set_nonblocking(true).unwrap(); 206 | socket.listen(128).unwrap(); 207 | TcpListener::from_std(socket.into()).unwrap() 208 | } 209 | 210 | async fn acceptor_main(server: Weak>, listener: TcpListener) { 211 | loop { 212 | let (stream, addr) = listener.accept().await.unwrap(); 213 | let Some(this) = server.upgrade() else { break }; 214 | let mut this = this.borrow_mut(); 215 | let (r, w) = stream.into_split(); 216 | this.clients = Some(Rc::new_cyclic(|weak| { 217 | let client = Client { 218 | weak: weak.clone(), 219 | tui: this.tui.clone(), 220 | log_prefix: addr.to_string(), 221 | next: this.clients.take(), 222 | prev: None, 223 | server: server.clone(), 224 | login: None, 225 | _reader: spawn(reader_main(weak.clone(), r)), 226 | request_queue: VecDeque::new(), 227 | request_queue_size: 0, 228 | response_queue: VecDeque::new(), 229 | writer: WriterState::NotWriting(w), 230 | timeout: None, 231 | }; 232 | if let Some(ref next) = client.next { 233 | next.borrow_mut().prev = Some(weak.clone()) 234 | } 235 | client.log(format_args!("connected")); 236 | RefCell::new(client) 237 | })) 238 | } 239 | } 240 | 241 | impl Server { 242 | pub fn new(tui: Rc, port: u16) -> Rc> { 243 | Rc::new_cyclic(|weak| { 244 | RefCell::new(Server { 245 | tui, 246 | clients: None, 247 | logins: FnvHashMap::default(), 248 | _acceptor: spawn(acceptor_main(weak.clone(), create_listener(port))), 249 | }) 250 | }) 251 | } 252 | 253 | fn login(&mut self, name: LocalStr, client: Weak>) { 254 | if let Some(old) = self.logins.insert(name, client) { 255 | upgrade_mut!(old, old); 256 | old.log(format_args!("logged in from another address")); 257 | old.login = None; 258 | old.disconnect_by_server(self) 259 | } 260 | } 261 | 262 | pub fn enqueue_request_group(&self, client: &str, group: Vec>>) { 263 | if let Some(client) = self.logins.get(client) { 264 | client.upgrade().unwrap().borrow_mut().enqueue_request_group(group) 265 | } else { 266 | let reason = local_fmt!("{} isn't connected", client); 267 | for x in group { 268 | x.borrow_mut().on_fail(reason.clone()) 269 | } 270 | } 271 | } 272 | 273 | fn estimate_load(&self, client: &str) -> usize { 274 | if let Some(client) = self.logins.get(client) { 275 | client.upgrade().unwrap().borrow().estimate_load() 276 | } else { 277 | usize::MAX 278 | } 279 | } 280 | 281 | pub fn load_balance<'a, T: Access>(&self, iter: impl IntoIterator) -> (usize, &'a T) { 282 | let mut iter = iter.into_iter().enumerate(); 283 | let mut best_access = iter.next().unwrap(); 284 | let mut best_load = self.estimate_load(best_access.1.get_client()); 285 | for access in iter { 286 | let load = self.estimate_load(access.1.get_client()); 287 | if load < best_load { 288 | best_load = load; 289 | best_access = access 290 | } 291 | } 292 | best_access 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /server/RustImpl/src/side.rs: -------------------------------------------------------------------------------- 1 | pub const BOTTOM: u8 = 0; 2 | pub const DOWN: u8 = 0; 3 | pub const YN: u8 = 0; 4 | 5 | pub const TOP: u8 = 1; 6 | pub const UP: u8 = 1; 7 | pub const YP: u8 = 1; 8 | 9 | pub const BACK: u8 = 2; 10 | pub const NORTH: u8 = 2; 11 | pub const ZN: u8 = 2; 12 | 13 | pub const FRONT: u8 = 3; 14 | pub const SOUTH: u8 = 3; 15 | pub const ZP: u8 = 3; 16 | 17 | pub const RIGHT: u8 = 4; 18 | pub const WEST: u8 = 4; 19 | pub const XN: u8 = 4; 20 | 21 | pub const LEFT: u8 = 5; 22 | pub const EAST: u8 = 5; 23 | pub const XP: u8 = 5; 24 | -------------------------------------------------------------------------------- /server/RustImpl/src/storage/chest.rs: -------------------------------------------------------------------------------- 1 | use super::super::access::InvAccess; 2 | use super::super::action::{ActionFuture, Call, List}; 3 | use super::super::factory::Factory; 4 | use super::super::item::{Item, ItemStack}; 5 | use super::super::util::{alive, spawn}; 6 | use super::{DepositResult, Extractor, IntoStorage, Provider, Storage}; 7 | use abort_on_drop::ChildTask; 8 | use flexstr::{local_str, LocalStr}; 9 | use std::{ 10 | cell::RefCell, 11 | cmp::min, 12 | rc::{Rc, Weak}, 13 | }; 14 | 15 | pub struct ChestConfig { 16 | pub accesses: Vec, 17 | } 18 | 19 | pub struct ChestStorage { 20 | weak: Weak>, 21 | config: ChestConfig, 22 | factory: Weak>, 23 | stacks: Vec>, 24 | inv_slot_to_deposit: usize, 25 | } 26 | 27 | struct ChestExtractor { 28 | weak: Weak>, 29 | inv_slot: usize, 30 | } 31 | 32 | impl IntoStorage for ChestConfig { 33 | type Output = ChestStorage; 34 | fn into_storage(self, factory: &Factory) -> Rc> { 35 | Rc::new_cyclic(|weak| { 36 | RefCell::new(Self::Output { 37 | weak: weak.clone(), 38 | config: self, 39 | factory: factory.weak.clone(), 40 | stacks: Vec::new(), 41 | inv_slot_to_deposit: 0, 42 | }) 43 | }) 44 | } 45 | } 46 | 47 | impl Storage for ChestStorage { 48 | fn update(&self, factory: &Factory) -> ChildTask> { 49 | let server = factory.borrow_server(); 50 | let access = server.load_balance(&self.config.accesses).1; 51 | let action = ActionFuture::from(List { addr: access.addr.clone(), side: access.inv_side }); 52 | server.enqueue_request_group(&access.client, vec![action.clone().into()]); 53 | let weak = self.weak.clone(); 54 | spawn(async move { 55 | let stacks = action.await?; 56 | alive_mut!(weak, this); 57 | this.stacks = stacks; 58 | upgrade_mut!(this.factory, factory); 59 | for (inv_slot, stack) in this.stacks.iter().enumerate() { 60 | if let Some(stack) = stack { 61 | factory.register_stored_item(stack.item.clone()).provide(Provider { 62 | priority: -stack.size, 63 | n_provided: stack.size.into(), 64 | extractor: Rc::new(ChestExtractor { weak: weak.clone(), inv_slot }), 65 | }); 66 | } 67 | } 68 | Ok(()) 69 | }) 70 | } 71 | 72 | fn cleanup(&mut self) { self.stacks.clear() } 73 | 74 | fn deposit_priority(&mut self, item: &Rc) -> Option { 75 | let mut empty_slot = None; 76 | let mut size_of_best_slot = None; 77 | for (inv_slot, stack) in self.stacks.iter().enumerate() { 78 | if let Some(stack) = stack { 79 | if stack.item == *item && stack.size < item.max_size { 80 | if let Some(best_size) = size_of_best_slot { 81 | if stack.size <= best_size { 82 | continue; 83 | } 84 | } 85 | size_of_best_slot = Some(stack.size); 86 | self.inv_slot_to_deposit = inv_slot 87 | } 88 | } else { 89 | empty_slot = Some(inv_slot) 90 | } 91 | } 92 | size_of_best_slot.or_else(|| { 93 | empty_slot.map(|x| { 94 | self.inv_slot_to_deposit = x; 95 | i32::MIN + 1 96 | }) 97 | }) 98 | } 99 | 100 | fn deposit(&mut self, factory: &Factory, stack: &ItemStack, bus_slot: usize) -> DepositResult { 101 | let inv_slot = self.inv_slot_to_deposit; 102 | let inv_stack = &mut self.stacks[inv_slot]; 103 | let n_deposited; 104 | if let Some(inv_stack) = inv_stack { 105 | n_deposited = min(stack.size, inv_stack.item.max_size - inv_stack.size); 106 | inv_stack.size += n_deposited 107 | } else { 108 | n_deposited = stack.size; 109 | *inv_stack = Some(stack.clone()) 110 | } 111 | let server = factory.borrow_server(); 112 | let access = server.load_balance(&self.config.accesses).1; 113 | let action = ActionFuture::from(Call { 114 | addr: access.addr.clone(), 115 | func: local_str!("transferItem"), 116 | args: vec![ 117 | access.bus_side.into(), 118 | access.inv_side.into(), 119 | n_deposited.into(), 120 | (bus_slot + 1).into(), 121 | (inv_slot + 1).into(), 122 | ], 123 | }); 124 | server.enqueue_request_group(&access.client, vec![action.clone().into()]); 125 | let task = spawn(async move { action.await.map(|_| ()) }); 126 | DepositResult { n_deposited, task } 127 | } 128 | } 129 | 130 | impl Extractor for ChestExtractor { 131 | fn extract(&self, factory: &Factory, size: i32, bus_slot: usize) -> ChildTask> { 132 | let inv_slot = self.inv_slot; 133 | let server = factory.borrow_server(); 134 | upgrade!(self.weak, this); 135 | let access = server.load_balance(&this.config.accesses).1; 136 | let action = ActionFuture::from(Call { 137 | addr: access.addr.clone(), 138 | func: local_str!("transferItem"), 139 | args: vec![ 140 | access.inv_side.into(), 141 | access.bus_side.into(), 142 | size.into(), 143 | (inv_slot + 1).into(), 144 | (bus_slot + 1).into(), 145 | ], 146 | }); 147 | server.enqueue_request_group(&access.client, vec![action.clone().into()]); 148 | let weak = self.weak.clone(); 149 | spawn(async move { 150 | action.await?; 151 | alive_mut!(weak, this); 152 | let inv_stack = &mut this.stacks[inv_slot]; 153 | let inv_size = &mut inv_stack.as_mut().unwrap().size; 154 | *inv_size -= size; 155 | if *inv_size <= 0 { 156 | *inv_stack = None; 157 | } 158 | Ok(()) 159 | }) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /server/RustImpl/src/storage/drawer.rs: -------------------------------------------------------------------------------- 1 | use super::super::access::InvAccess; 2 | use super::super::action::{ActionFuture, Call, List}; 3 | use super::super::factory::Factory; 4 | use super::super::item::{Filter, Item, ItemStack}; 5 | use super::super::util::{alive, spawn}; 6 | use super::{DepositResult, Extractor, IntoStorage, Provider, Storage}; 7 | use abort_on_drop::ChildTask; 8 | use flexstr::{local_str, LocalStr}; 9 | use std::{ 10 | cell::RefCell, 11 | rc::{Rc, Weak}, 12 | }; 13 | 14 | pub struct DrawerConfig { 15 | pub accesses: Vec, 16 | pub filters: Vec, 17 | } 18 | 19 | pub struct DrawerStorage { 20 | weak: Weak>, 21 | config: DrawerConfig, 22 | factory: Weak>, 23 | } 24 | 25 | struct DrawerExtractor { 26 | weak: Weak>, 27 | inv_slot: usize, 28 | } 29 | 30 | impl IntoStorage for DrawerConfig { 31 | type Output = DrawerStorage; 32 | fn into_storage(self, factory: &Factory) -> Rc> { 33 | Rc::new_cyclic(|weak| { 34 | RefCell::new(Self::Output { weak: weak.clone(), config: self, factory: factory.weak.clone() }) 35 | }) 36 | } 37 | } 38 | 39 | impl Storage for DrawerStorage { 40 | fn update(&self, factory: &Factory) -> ChildTask> { 41 | let server = factory.borrow_server(); 42 | let access = server.load_balance(&self.config.accesses).1; 43 | let action = ActionFuture::from(List { addr: access.addr.clone(), side: access.inv_side }); 44 | server.enqueue_request_group(&access.client, vec![action.clone().into()]); 45 | let weak = self.weak.clone(); 46 | spawn(async move { 47 | let stacks = action.await?; 48 | alive!(weak, this); 49 | upgrade_mut!(this.factory, factory); 50 | for (inv_slot, stack) in stacks.into_iter().enumerate() { 51 | if let Some(stack) = stack { 52 | factory.register_stored_item(stack.item).provide(Provider { 53 | priority: i32::MIN, 54 | n_provided: stack.size.into(), 55 | extractor: Rc::new(DrawerExtractor { weak: weak.clone(), inv_slot }), 56 | }); 57 | } 58 | } 59 | Ok(()) 60 | }) 61 | } 62 | 63 | fn cleanup(&mut self) {} 64 | 65 | fn deposit_priority(&mut self, item: &Rc) -> Option { 66 | for filter in &self.config.filters { 67 | if filter.apply(item) { 68 | return Some(i32::MAX); 69 | } 70 | } 71 | None 72 | } 73 | 74 | fn deposit(&mut self, factory: &Factory, stack: &ItemStack, bus_slot: usize) -> DepositResult { 75 | let n_deposited = stack.size; 76 | let server = factory.borrow_server(); 77 | let access = server.load_balance(&self.config.accesses).1; 78 | let action = ActionFuture::from(Call { 79 | addr: access.addr.clone(), 80 | func: local_str!("transferItem"), 81 | args: vec![access.bus_side.into(), access.inv_side.into(), n_deposited.into(), (bus_slot + 1).into()], 82 | }); 83 | server.enqueue_request_group(&access.client, vec![action.clone().into()]); 84 | let task = spawn(async move { action.await.map(|_| ()) }); 85 | DepositResult { n_deposited, task } 86 | } 87 | } 88 | 89 | impl Extractor for DrawerExtractor { 90 | fn extract(&self, factory: &Factory, size: i32, bus_slot: usize) -> ChildTask> { 91 | upgrade!(self.weak, this); 92 | let server = factory.borrow_server(); 93 | let access = server.load_balance(&this.config.accesses).1; 94 | let action = ActionFuture::from(Call { 95 | addr: access.addr.clone(), 96 | func: local_str!("transferItem"), 97 | args: vec![ 98 | access.inv_side.into(), 99 | access.bus_side.into(), 100 | size.into(), 101 | (self.inv_slot + 1).into(), 102 | (bus_slot + 1).into(), 103 | ], 104 | }); 105 | server.enqueue_request_group(&access.client, vec![action.clone().into()]); 106 | spawn(async move { action.await.map(|_| ()) }) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /server/RustImpl/src/storage/me.rs: -------------------------------------------------------------------------------- 1 | use super::super::access::MEAccess; 2 | use super::super::action::{ActionFuture, Call, ListME, XferME}; 3 | use super::super::factory::Factory; 4 | use super::super::item::{Item, ItemStack}; 5 | use super::super::util::{alive, spawn}; 6 | use super::{DepositResult, Extractor, IntoStorage, Provider, Storage}; 7 | use abort_on_drop::ChildTask; 8 | use flexstr::{local_str, LocalStr}; 9 | use fnv::FnvHashMap; 10 | use std::{ 11 | cell::RefCell, 12 | rc::{Rc, Weak}, 13 | }; 14 | 15 | pub struct MEConfig { 16 | pub accesses: Vec, 17 | } 18 | 19 | pub struct MEStorage { 20 | weak: Weak>, 21 | config: MEConfig, 22 | factory: Weak>, 23 | access_for_item: FnvHashMap, usize>, 24 | } 25 | 26 | struct MEExtractor { 27 | weak: Weak>, 28 | item: Rc, 29 | } 30 | 31 | impl IntoStorage for MEConfig { 32 | type Output = MEStorage; 33 | fn into_storage(self, factory: &Factory) -> Rc> { 34 | Rc::new_cyclic(|weak| { 35 | RefCell::new(Self::Output { 36 | weak: weak.clone(), 37 | config: self, 38 | factory: factory.weak.clone(), 39 | access_for_item: FnvHashMap::default(), 40 | }) 41 | }) 42 | } 43 | } 44 | 45 | impl Storage for MEStorage { 46 | fn update(&self, factory: &Factory) -> ChildTask> { 47 | let server = factory.borrow_server(); 48 | let access = server.load_balance(&self.config.accesses).1; 49 | let action = ActionFuture::from(ListME { addr: access.me_addr.clone() }); 50 | server.enqueue_request_group(&access.client, vec![action.clone().into()]); 51 | let weak = self.weak.clone(); 52 | spawn(async move { 53 | let stacks = action.await?; 54 | alive!(weak, this); 55 | upgrade_mut!(this.factory, factory); 56 | for mut stack in stacks.into_iter() { 57 | Rc::get_mut(&mut stack.item).unwrap().others.remove(&"isCraftable".into()); 58 | factory.register_stored_item(stack.item.clone()).provide(Provider { 59 | priority: i32::MAX, 60 | n_provided: stack.size.into(), 61 | extractor: Rc::new(MEExtractor { weak: weak.clone(), item: stack.item }), 62 | }) 63 | } 64 | Ok(()) 65 | }) 66 | } 67 | 68 | fn cleanup(&mut self) { self.access_for_item.clear() } 69 | 70 | fn deposit_priority(&mut self, _item: &Rc) -> Option { Some(i32::MIN) } 71 | 72 | fn deposit(&mut self, factory: &Factory, stack: &ItemStack, bus_slot: usize) -> DepositResult { 73 | let n_deposited = stack.size; 74 | let server = factory.borrow_server(); 75 | let access = server.load_balance(&self.config.accesses).1; 76 | let action = ActionFuture::from(Call { 77 | addr: access.transposer_addr.clone(), 78 | func: local_str!("transferItem"), 79 | args: vec![ 80 | access.bus_side.into(), 81 | access.me_side.into(), 82 | n_deposited.into(), 83 | (bus_slot + 1).into(), 84 | 9.into(), 85 | ], 86 | }); 87 | server.enqueue_request_group(&access.client, vec![action.clone().into()]); 88 | let task = spawn(async move { action.await.map(|_| ()) }); 89 | DepositResult { n_deposited, task } 90 | } 91 | } 92 | 93 | impl Extractor for MEExtractor { 94 | fn extract(&self, factory: &Factory, size: i32, bus_slot: usize) -> ChildTask> { 95 | upgrade_mut!(self.weak, this); 96 | let server = factory.borrow_server(); 97 | let accesses = &this.config.accesses; 98 | let access = *this.access_for_item.entry(self.item.clone()).or_insert_with(|| server.load_balance(accesses).0); 99 | let access = &this.config.accesses[access]; 100 | let action = ActionFuture::from(XferME { 101 | me_addr: access.me_addr.clone(), 102 | me_slot: access.me_slot, 103 | filter: self.item.serialize(), 104 | size, 105 | transposer_addr: access.transposer_addr.clone(), 106 | transposer_args: vec![ 107 | access.me_side.into(), 108 | access.bus_side.into(), 109 | size.into(), 110 | (access.me_slot + 1).into(), 111 | (bus_slot + 1).into(), 112 | ], 113 | }); 114 | server.enqueue_request_group(&access.client, vec![action.clone().into()]); 115 | spawn(async move { action.await.map(|_| ()) }) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /server/RustImpl/src/storage/mod.rs: -------------------------------------------------------------------------------- 1 | use super::factory::Factory; 2 | use super::item::{Item, ItemStack}; 3 | use abort_on_drop::ChildTask; 4 | use flexstr::LocalStr; 5 | use std::{ 6 | cell::{Cell, RefCell}, 7 | cmp::Ordering, 8 | rc::Rc, 9 | }; 10 | 11 | pub struct DepositResult { 12 | pub n_deposited: i32, 13 | pub task: ChildTask>, 14 | } 15 | 16 | pub trait Storage: 'static { 17 | fn update(&self, factory: &Factory) -> ChildTask>; 18 | fn cleanup(&mut self); 19 | fn deposit_priority(&mut self, item: &Rc) -> Option; 20 | fn deposit(&mut self, factory: &Factory, stack: &ItemStack, bus_slot: usize) -> DepositResult; 21 | } 22 | 23 | pub trait IntoStorage { 24 | type Output: Storage; 25 | fn into_storage(self, factory: &Factory) -> Rc>; 26 | } 27 | 28 | pub trait Extractor { 29 | fn extract(&self, factory: &Factory, size: i32, bus_slot: usize) -> ChildTask>; 30 | } 31 | 32 | pub struct Provider { 33 | priority: i32, 34 | pub n_provided: Cell, 35 | pub extractor: Rc, 36 | } 37 | 38 | impl PartialEq for Provider { 39 | fn eq(&self, other: &Self) -> bool { self.priority == other.priority } 40 | } 41 | 42 | impl Eq for Provider {} 43 | 44 | impl PartialOrd for Provider { 45 | fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } 46 | } 47 | 48 | impl Ord for Provider { 49 | fn cmp(&self, other: &Self) -> Ordering { self.priority.cmp(&other.priority) } 50 | } 51 | 52 | mod chest; 53 | mod drawer; 54 | mod me; 55 | pub use chest::*; 56 | pub use drawer::*; 57 | pub use me::*; 58 | -------------------------------------------------------------------------------- /server/RustImpl/src/util.rs: -------------------------------------------------------------------------------- 1 | use abort_on_drop::ChildTask; 2 | use flexstr::{local_str, LocalStr}; 3 | use std::{ 4 | cell::RefCell, 5 | future::Future, 6 | pin::Pin, 7 | rc::{Rc, Weak}, 8 | task::{Context, Poll, Waker}, 9 | }; 10 | use tokio::task::spawn_local; 11 | 12 | pub fn spawn(future: impl Future + 'static) -> ChildTask { spawn_local(future).into() } 13 | 14 | pub async fn join_tasks(tasks: Vec>>) -> Result<(), LocalStr> { 15 | let mut result: Result<(), Vec> = Ok(()); 16 | for task in tasks { 17 | if let Err(e) = task.await.unwrap() { 18 | if let Err(ref mut result) = result { 19 | result.push(local_str!("; ")); 20 | result.push(e) 21 | } else { 22 | result = Err(vec![e]) 23 | } 24 | } 25 | } 26 | result.map_err(|x| x.into_iter().collect()) 27 | } 28 | 29 | pub async fn join_outputs(tasks: Vec>>) -> Result, LocalStr> { 30 | let mut result: Result, Vec> = Ok(Vec::new()); 31 | for task in tasks { 32 | match task.await.unwrap() { 33 | Err(e) => { 34 | if let Err(ref mut result) = result { 35 | result.push(local_str!("; ")); 36 | result.push(e) 37 | } else { 38 | result = Err(vec![e]) 39 | } 40 | } 41 | Ok(output) => { 42 | if let Ok(ref mut result) = result { 43 | result.push(output) 44 | } 45 | } 46 | } 47 | } 48 | result.map_err(|x| x.into_iter().collect()) 49 | } 50 | 51 | struct LocalOneShotState { 52 | result: Option>, 53 | waker: Option, 54 | } 55 | 56 | fn send(state: Weak>>, result: Result) { 57 | if let Some(state) = state.upgrade() { 58 | let mut state = state.borrow_mut(); 59 | state.result = Some(result); 60 | if let Some(waker) = state.waker.take() { 61 | waker.wake() 62 | } 63 | } 64 | } 65 | 66 | pub struct LocalReceiver(Rc>>); 67 | 68 | impl Future for LocalReceiver { 69 | type Output = Result; 70 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 71 | let this = self.get_mut(); 72 | let mut this = this.0.borrow_mut(); 73 | if let Some(result) = this.result.take() { 74 | Poll::Ready(result) 75 | } else { 76 | this.waker = Some(cx.waker().clone()); 77 | Poll::Pending 78 | } 79 | } 80 | } 81 | 82 | pub struct LocalSender(Option>>>); 83 | 84 | impl Drop for LocalSender { 85 | fn drop(&mut self) { 86 | if let Some(state) = self.0.take() { 87 | send(state, Err(local_str!("sender died"))) 88 | } 89 | } 90 | } 91 | 92 | impl LocalSender { 93 | pub fn send(mut self, result: Result) { send(self.0.take().unwrap(), result) } 94 | } 95 | 96 | pub fn make_local_one_shot() -> (LocalSender, LocalReceiver) { 97 | let state = Rc::new(RefCell::new(LocalOneShotState { result: None, waker: None })); 98 | (LocalSender(Some(Rc::downgrade(&state))), LocalReceiver(state)) 99 | } 100 | 101 | macro_rules! upgrade { 102 | ($e:expr, $v:ident) => { 103 | let $v = $e.upgrade().unwrap(); 104 | let $v = $v.borrow(); 105 | let $v = &*$v; 106 | }; 107 | } 108 | 109 | macro_rules! upgrade_mut { 110 | ($e:expr, $v:ident) => { 111 | let $v = $e.upgrade().unwrap(); 112 | let mut $v = $v.borrow_mut(); 113 | let $v = &mut *$v; 114 | }; 115 | } 116 | 117 | pub fn alive(weak: &Weak) -> Result, LocalStr> { weak.upgrade().ok_or_else(|| local_str!("owner died")) } 118 | 119 | macro_rules! alive { 120 | ($e:expr, $v:ident) => { 121 | let $v = alive(&$e)?; 122 | let $v = $v.borrow(); 123 | let $v = &*$v; 124 | }; 125 | } 126 | 127 | macro_rules! alive_mut { 128 | ($e:expr, $v:ident) => { 129 | let $v = alive(&$e)?; 130 | let mut $v = $v.borrow_mut(); 131 | let $v = &mut *$v; 132 | }; 133 | } 134 | -------------------------------------------------------------------------------- /server/forward.sh: -------------------------------------------------------------------------------- 1 | while true; do 2 | ssh -t -i ../../lckey/repo ec2-user@leu-235.com "sudo lsof -n -i :1847 | grep LISTEN | awk '{ print \$2 }' | sort | uniq | xargs kill" 3 | ssh -o "ExitOnForwardFailure yes" -i ../../lckey/repo -N -R :1847:localhost:1847 ec2-user@leu-235.com 4 | done 5 | -------------------------------------------------------------------------------- /workbench2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyb0124/OCRemote/38218cf5367a7f8e9cd8799cff14649a2712fdec/workbench2.gif --------------------------------------------------------------------------------