-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmega_inventory.lua
More file actions
480 lines (361 loc) · 13.8 KB
/
mega_inventory.lua
File metadata and controls
480 lines (361 loc) · 13.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
--- Mega inventory: Combines multiple inventories into a single one, for ease of use.
local expect = require "cc.expect".expect
local new_parallelism_handler = require "parallelism_handler"
---@class InventoryReference
---@field inventory Inventory The inventory.
---@field name string The name of the inventory.
---@class MegaInventory
---@field inventories InventoryReference[] The inventories to combine.
---@field last_list table<integer, item> The last list of items in the inventory. Slots above the first inventory's size are the second inventory, and so on...
local mega_inventory = {}
local mega_inventory_mt = {}
mega_inventory_mt.__index = mega_inventory
--- Sum up values in the input table.
---@param values number[] The values to sum up.
---@return number sum The sum of the values.
local function sum(values)
local sum = 0
for _, value in ipairs(values) do
sum = sum + value
end
return sum
end
--- Create a new mega inventory.
---@param ... Inventory The inventories to combine.
---@return MegaInventory mega_inventory The new mega inventory.
function mega_inventory.new(...)
local inventories = {...}
local inventory_references = {}
for i, inventory in ipairs(inventories) do
expect(i, inventory, "table")
table.insert(inventory_references, {
inventory = inventory,
name = peripheral.getName(inventory),
})
end
return setmetatable({
inventories = inventory_references,
}, mega_inventory_mt)
end
--- Get sizes of all inventories, in the order of `self.inventories`.
---@return integer[] sizes The sizes of the inventories.
---@overload fun(ph: ParallelismHandler): nil Internal use only.
function mega_inventory:sizes(ph)
local no_execute = ph ~= nil
ph = ph or new_parallelism_handler()
for _, inventory_reference in ipairs(self.inventories) do
ph:add_task(inventory_reference.inventory.size)
end
if no_execute then
return
end
return ph:execute()
end
--- Get the inventory size.
---@return integer size The size of the inventory.
function mega_inventory:size()
return sum(self:sizes())
end
--- List all items in the inventory.
---@return table<integer, item> items The items in the inventory.
function mega_inventory:list()
local list_handler = new_parallelism_handler()
local size_handler = new_parallelism_handler()
local sizes, lists;
self:sizes(size_handler)
for _, inventory_reference in ipairs(self.inventories) do
list_handler:add_task(inventory_reference.inventory.list)
end
parallel.waitForAll(
function()
sizes = size_handler:execute()
end,
function()
lists = list_handler:execute()
end
)
local items = {}
for i, item_list in ipairs(lists) do
local slot_offset = 0
for j = 1, i - 1 do
slot_offset = slot_offset + sizes[j]
end
for slot, item in pairs(item_list) do
items[slot + slot_offset] = item
end
end
self.last_list = items
return items
end
--- Get item detail of an item in a given mega slot.
---
--- Note that this method is not cached, and will always fetch the item detail from the peripheral.
---@param slot integer The slot to get the item detail of.
---@param sizes integer[]? The sizes of the inventories. Mainly used internally, but can be used to optimize get_item_detail calls.
---@return item? item The item in the slot, or nil if the slot is empty.
function mega_inventory:get_item_detail(slot, sizes)
expect(1, slot, "number")
expect(2, sizes, "table", "nil")
local inv_ref, slot = self:calc_inv_via_slot(slot, sizes)
return inv_ref.inventory.getItemDetail(slot)
end
--- Calculate the inventory and actual slot of a slot in the mega inventory.
---@param slot integer The slot to calculate the inventory and slot of.
---@param sizes integer[]? The sizes of the inventories. Used internally when pushing many items, to avoid recalculating sizes multiple times.
---@return InventoryReference inventory_ref The inventory the slot is in.
---@return integer slot The slot within that inventory.
function mega_inventory:calc_inv_via_slot(slot, sizes)
expect(1, slot, "number")
sizes = sizes or self:sizes()
local slot_offset = 0
for i, size in ipairs(sizes) do
if slot > slot_offset and slot <= slot_offset + size then
return self.inventories[i], slot - slot_offset
end
slot_offset = slot_offset + size
end
error("Slot out of range", 2)
end
--- Push items from this mega inventory to a named inventory.
---@param name string The name of the inventory to push to.
---@param item_name string The name of the item to push.
---@param count integer The maximum number of items to push.
---@param result_slot integer? The slot to push to. If not provided, the first available slot is used.
---@return integer pushed The number of items pushed.
function mega_inventory:push_items(name, item_name, count, result_slot)
expect(1, name, "string")
expect(2, item_name, "string")
expect(3, count, "number")
expect(4, result_slot, "number", "nil")
local items = self.last_list
if not items then
items = self:list()
end
local ph = new_parallelism_handler()
local should_push = 0
local sizes = self:sizes()
for slot, item in pairs(items) do
if item.name == item_name then
local inventory_ref, inv_slot = self:calc_inv_via_slot(slot, sizes)
ph:add_task(inventory_ref.inventory.pushItems, name, inv_slot, count - should_push, result_slot)
should_push = should_push + math.min(item.count, count - should_push)
if should_push >= count then
break
end
end
end
-- Invalidate the last list, since we have altered the inventory.
self.last_list = nil
return sum(ph:execute()) -- The actual amounts pushed are returned by the peripheral calls.
end
--- Push items to other spots in the mega inventory.
---@param from_slot integer The slot to push from.
---@param to_slot integer The slot to push to.
---@param count integer? The maximum number of items to push. If not provided, all items are pushed.
---@param sizes integer[]? The sizes of the inventories. Used internally when pushing many items, to avoid recalculating sizes multiple times.
---@return integer pushed The number of items pushed.
function mega_inventory:push_items_internal(from_slot, to_slot, count, sizes)
expect(1, from_slot, "number")
expect(2, to_slot, "number")
expect(3, count, "number", "nil")
expect(4, sizes, "table", "nil")
local from_inv_ref, from_slot = self:calc_inv_via_slot(from_slot, sizes)
local to_inv_ref, to_slot = self:calc_inv_via_slot(to_slot, sizes)
return from_inv_ref.inventory.pushItems(to_inv_ref.name, from_slot, count, to_slot)
end
--- Push a list of items from this mega inventory to a named inventory.
---@param name string The name of the inventory to push to.
---@param items table<string, true> The items to push.
---@param count integer The maximum number of items to push.
---@return integer pushed The number of items pushed.
function mega_inventory:batch_push_items(name, items, count)
expect(1, name, "string")
expect(2, items, "table")
for k in pairs(items) do
if type(k) ~= "string" then
error(("Bad argument #2: Table has key of type '%s', expected only 'string'"):format(type(k)), 2)
end
end
expect(3, count, "number")
if not self.last_list then
self:list()
end
local ph = new_parallelism_handler()
local should_push = 0
local sizes = self:sizes()
for slot, item in pairs(self.last_list) do
if items[item.name] then
local inventory_ref, inv_slot = self:calc_inv_via_slot(slot, sizes)
ph:add_task(inventory_ref.inventory.pushItems, name, inv_slot, count - should_push)
should_push = should_push + math.min(item.count, count - should_push)
if should_push >= count then
break
end
end
end
-- Invalidate the last list, since we have altered the inventory.
self.last_list = nil
return sum(ph:execute()) -- The actual amounts pushed are returned by the peripheral calls.
end
--- Pull items from a named inventory to this mega inventory.
---@param name string The name of the inventory to pull from.
---@param item_name string The name of the item to pull.
---@param count integer The maximum number of items to pull.
---@return integer pulled The number of items pulled.
function mega_inventory:pull_items(name, item_name, count)
expect(1, name, "string")
expect(2, item_name, "string")
expect(3, count, "number")
--- List of items in the other inventory.
local other_inventory = peripheral.call(name, "list") --[[@as table<integer, item>]]
local ph = new_parallelism_handler()
local actual_moved = 0
local should_move = 0
-- Attempt to pull to each inventory in the mega inventory.
for _, inventory_ref in ipairs(self.inventories) do
local pullItemsRef = inventory_ref.inventory.pullItems
for slot, item in pairs(other_inventory) do
if item.name == item_name then
ph:add_task(pullItemsRef, name, slot, count - should_move)
should_move = should_move + math.min(item.count, count - should_move)
if should_move >= count then
break
end
end
end
actual_moved = actual_moved + sum(ph:execute())
if actual_moved >= count then
break
end
should_move = actual_moved
other_inventory = peripheral.call(name, "list") --[[@as table<integer, item>]]
if not next(other_inventory) then
break
end
end
-- Invalidate the last list, since we have altered the inventory.
self.last_list = nil
return actual_moved
end
--- Pull *all* items from a named inventory to this mega inventory.
---@param name string The name of the inventory to pull from.
---@return integer pulled The number of items pulled.
function mega_inventory:pull_all_items(name)
expect(1, name, "string")
--- List of items in the other inventory.
local other_inventory = peripheral.call(name, "list") --[[@as table<integer, item>]]
local ph = new_parallelism_handler()
local actual_moved = 0
-- Attempt to pull to each inventory in the mega inventory.
for _, inventory_ref in ipairs(self.inventories) do
local pullItemsRef = inventory_ref.inventory.pullItems
for slot, item in pairs(other_inventory) do
ph:add_task(pullItemsRef, name, slot, item.count)
end
actual_moved = actual_moved + sum(ph:execute())
other_inventory = peripheral.call(name, "list") --[[@as table<integer, item>]]
if not next(other_inventory) then
break
end
end
-- Invalidate the last list, since we have altered the inventory.
self.last_list = nil
return actual_moved
end
--- Count the given items in the inventory.
---@param item_name string The name of the item to count.
---@return integer count The number of items in the inventory.
function mega_inventory:count(item_name)
expect(1, item_name, "string")
if not self.last_list then
self:list()
end
local count = 0
for _, item in pairs(self.last_list) do
if item.name == item_name then
count = count + item.count
end
end
return count
end
--- Defragment the inventory.
function mega_inventory:defragment()
local list = self:list()
local sizes = self:sizes()
local item_limit_cache = {} ---@type table<string, integer>
local parallelism_handler = new_parallelism_handler()
-- Determine which slots have items.
local slots_with_items = {} ---@type integer[]
for slot in pairs(list) do
table.insert(slots_with_items, slot)
end
table.sort(slots_with_items)
-- Go through each slot, and get the item data.
-- We will likely end up sending out more `getItemDetail` calls than necessary, but this should be fine.
for i, slot in ipairs(slots_with_items) do
parallelism_handler:add_task(function()
if not item_limit_cache[list[slot].name] then
local data = self:get_item_detail(slot, sizes)
if data then
item_limit_cache[data.name] = data.maxCount
end
end
end)
end
parallelism_handler:execute()
local len = #slots_with_items
local current = 1
while current < len do
local list_entry = list[slots_with_items[current]]
local cache_entry = item_limit_cache[list_entry.name]
if list_entry.count < cache_entry then
for search_index = len, current + 1, -1 do
local other_list_entry = list[slots_with_items[search_index]]
if other_list_entry.name == list_entry.name then
local to_move = math.min(cache_entry - list_entry.count, other_list_entry.count)
local from_slot, to_slot = slots_with_items[search_index], slots_with_items[current]
parallelism_handler:add_task(function()
self:push_items_internal(from_slot, to_slot, to_move, sizes)
end)
list_entry.count = list_entry.count + to_move
other_list_entry.count = other_list_entry.count - to_move
if other_list_entry.count == 0 then
table.remove(slots_with_items, search_index)
len = len - 1
end
if list_entry.count >= cache_entry then
break
end
end
end
end
current = current + 1
end
parallelism_handler:execute()
end
--- Add an inventory to the mega inventory.
---@param inventory Inventory The inventory to add.
---@return MegaInventory self The mega inventory.
function mega_inventory:add_inventory(inventory)
expect(1, inventory, "table")
table.insert(self.inventories, {
inventory = inventory,
name = peripheral.getName(inventory),
})
return self
end
--- Remove an inventory from the mega inventory.
---@param inventory_name string The inventory to remove.
---@return MegaInventory self The mega inventory.
function mega_inventory:remove_inventory(inventory_name)
expect(1, inventory_name, "string")
for i, inventory_reference in ipairs(self.inventories) do
if inventory_reference.name == inventory_name then
table.remove(self.inventories, i)
break
end
end
self.last_list = nil
return self
end
return mega_inventory