diff --git a/milo/Milo.lua b/milo/Milo.lua index df1cbe9..cdb16af 100644 --- a/milo/Milo.lua +++ b/milo/Milo.lua @@ -69,7 +69,7 @@ local context = { _G._p = context --debug Event.on('storage_offline', function() - Milo:showError('A storage chest has gone offline - See configuration screen') + Milo:showError('A storage chest has gone offline - Review configuration') end) Milo:init(context) diff --git a/milo/apis/milo.lua b/milo/apis/milo.lua index 6c5668e..b00749e 100644 --- a/milo/apis/milo.lua +++ b/milo/apis/milo.lua @@ -247,7 +247,7 @@ function Milo:makeRequest(item, count, callback) end function Milo:eject(item, count) - count = self.context.storage:provide(item, count) + count = self.context.storage:export(self.context.storage.localName, nil, count, item) turtle.emptyInventory() return count end diff --git a/milo/apis/storage.lua b/milo/apis/storage.lua index 3cd3631..e0f3528 100644 --- a/milo/apis/storage.lua +++ b/milo/apis/storage.lua @@ -1,6 +1,7 @@ local class = require('class') local Event = require('event') local InventoryAdapter = require('inventoryAdapter') +local itemDB = require('itemDB') local Peripheral = require('peripheral') local Util = require('util') @@ -13,11 +14,8 @@ function Storage:init(args) local defaults = { remoteDefaults = { }, dirty = true, -listCount = 0, activity = { }, storageOnline = true, - hits = 0, - misses = 0, lastRefresh = os.clock(), } Util.merge(self, defaults) @@ -27,12 +25,11 @@ listCount = 0, self.localName = modem.getNameLocal() Event.on({ 'device_attach', 'device_detach' }, function(e, dev) -_debug('%s: %s', e, tostring(dev)) +_G._debug('%s: %s', e, tostring(dev)) self:initStorage() end) Event.onInterval(15, function() self:showStorage() - _debug('STORAGE: cache: %d/%d', self.hits, self.misses) end) end @@ -45,19 +42,11 @@ function Storage:showStorage() end end if #t > 0 then - _debug('Adapter:') + _G._debug('Adapter:') for _, k in pairs(t) do - _debug(' offline: ' .. k) + _G._debug(' offline: ' .. k) end - _debug('') - end -end - -function Storage:setOnline(online) - if online ~= self.storageOnline then - self.storageOnline = online - os.queueEvent(self.storageOnline and 'storage_online' or 'storage_offline', online) - _debug('Storage: %s', self.storageOnline and 'online' or 'offline') + _G._debug('') end end @@ -68,7 +57,7 @@ end function Storage:initStorage() local online = true - _debug('Initializing storage') + _G._debug('Initializing storage') for k,v in pairs(self.remoteDefaults) do if v.adapter then v.adapter.online = not not device[k] @@ -82,7 +71,11 @@ function Storage:initStorage() end end - self:setOnline(online) + if online ~= self.storageOnline then + self.storageOnline = online + os.queueEvent(self.storageOnline and 'storage_online' or 'storage_offline', online) + _G._debug('Storage: %s', self.storageOnline and 'online' or 'offline') + end end function Storage:filterActive(mtype, filter) @@ -138,29 +131,33 @@ end function Storage:refresh(throttle) self.dirty = true self.lastRefresh = os.clock() -_debug('STORAGE: Forcing full refresh') +_G._debug('STORAGE: Forcing full refresh') for _, adapter in self:onlineAdapters() do adapter.dirty = true end return self:listItems(throttle) end +local function Timer() + local ct = os.clock() + return function() + return os.clock() - ct + end +end + -- provide a consolidated list of items function Storage:listItems(throttle) if not self.dirty then - return self.items + return self.cache end -self.listCount = self.listCount + 1 ---_debug(self.listCount) -local ct = os.clock() +-- TODO: is there any reason now to maintain 2 lists local cache = { } - local items = { } throttle = throttle or Util.throttle() + local timer = Timer() for _, adapter in self:onlineAdapters() do if adapter.dirty then ---_debug('STORAGE: refresh: ' .. adapter.name) adapter:listItems(throttle) adapter.dirty = false end @@ -172,8 +169,6 @@ local ct = os.clock() entry.count = v.count entry.key = key cache[key] = entry - items[key] = entry --- table.insert(items, entry) else entry.count = entry.count + v.count end @@ -181,76 +176,80 @@ local ct = os.clock() throttle() end end -_debug('STORAGE: refresh in ' .. (os.clock() - ct)) +_G._debug('STORAGE: refresh in ' .. timer()) self.dirty = false self.cache = cache - self.items = items - return items + return cache +end + +function Storage:updateCache(adapter, key, count) + local entry = adapter.cache[key] + + if not entry then + if count < 0 then + adapter.dirty = true + self.dirty = true + else + entry = Util.shallowCopy(itemDB:get(key)) + entry.count = count + entry.key = key + adapter.cache[key] = entry + end + else + entry.count = entry.count + count + if entry.count <= 0 then + adapter.cache[key] = nil + end + end end function Storage:export(target, slot, count, item) - return self:provide(item, count, slot, target) -end - -function Storage:provide(item, qty, slot, direction) local total = 0 - local key = item.key or table.concat({ item.name, item.damage, item.nbtHash }, ':') - for _, adapter in self:onlineAdapters() do - if adapter.cache and adapter.cache[key] then - local amount = adapter:provide(item, qty, slot, direction or self.localName) - if amount > 0 then - self.hits = self.hits + 1 + + local function provide(adapter) + local amount = adapter:provide(item, count, slot, target or self.localName) + if amount > 0 then -- _debug('EXT: %s(%d): %s -> %s%s', -- item.name, amount, adapter.name, direction or self.localName, -- slot and string.format('[%d]', slot) or '') - self.dirty = true - adapter.dirty = true - end - qty = qty - amount - total = total + amount - if qty <= 0 then + self:updateCache(adapter, key, -amount) + self:updateCache(self, key, -amount) + end + count = count - amount + total = total + amount + end + + -- request from adapters with this item + for _, adapter in self:onlineAdapters() do + if adapter.cache and adapter.cache[key] then + provide(adapter) + if count <= 0 then return total end end end - _debug('STORAGE: MISS: %s - %d', key, qty) - self.misses = self.misses + 1 + _G._debug('STORAGE: MISS: %s - %d', key, count) + -- not found - scan all others for _, adapter in self:onlineAdapters() do - local amount = adapter:provide(item, qty, slot, direction or self.localName) - if amount > 0 then ---_debug('EXT: %s(%d): %s -> %s%s', --- item.name, amount, adapter.name, direction or self.localName, --- slot and string.format('[%d]', slot) or '') - self.dirty = true - adapter.dirty = true - end - qty = qty - amount - total = total + amount - if qty <= 0 then - break + if not adapter.cache or not adapter.cache[key] then + provide(adapter) + if count <= 0 then + _G._debug('STORAGE: FOUND: %s - %d', key, count) + break + end end end return total end -function Storage:trash(source, slot, count) - local trashcan = Util.find(self.remoteDefaults, 'mtype', 'trashcan') - if trashcan and trashcan.adapter and trashcan.adapter.online then ---_debug('TRA: %s[%d] (%d)', source or self.localName, slot, count or 64) --- return trashcan.adapter.pullItems(source or self.localName, slot, count) - end - return 0 -end - function Storage:import(source, slot, count, item) local total = 0 - - local key = table.concat({ item.name, item.damage, item.nbtHash }, ':') + local key = item.key or table.concat({ item.name, item.damage, item.nbtHash }, ':') if not self.cache then self:listItems() @@ -259,21 +258,16 @@ function Storage:import(source, slot, count, item) local function insert(adapter) local amount = adapter:insert(slot, count, nil, source or self.localName) if amount > 0 then + _G._debug('INS: %s(%d): %s[%d] -> %s', item.name, amount, source or self.localName, slot, adapter.name) - self.dirty = true - adapter.dirty = true - local entry = self.activity[key] or 0 - self.activity[key] = entry + amount ---[[ - local cached = adapter.cache[key] - if cached then - cached.count = cached.count + amount - else - end -]] + self:updateCache(adapter, key, amount) + self:updateCache(self, key, amount) + + -- record that we have imported this item into storage during this cycle + self.activity[key] = (self.activity[key] or 0) + amount end count = count - amount total = total + amount @@ -281,8 +275,7 @@ _G._debug('INS: %s(%d): %s[%d] -> %s', -- find a chest locked with this item for remote in self:onlineAdapters() do - -- TODO: proper checking using ignore dmg/nbt - if remote.lock == key or remote.lock == item.name then + if remote.lock == key then insert(remote.adapter) if count > 0 then -- TODO: only if void flag set total = total + self:trash(source, slot, count) @@ -291,10 +284,11 @@ _G._debug('INS: %s(%d): %s[%d] -> %s', end end - if self.cache[key] then -- is this item in some chest + -- is this item in some chest + if self.cache[key] then for _, adapter in self:onlineAdapters() do if count <= 0 then - break + return total end if adapter.cache and adapter.cache[key] and not adapter.lock then insert(adapter) @@ -315,4 +309,16 @@ _G._debug('INS: %s(%d): %s[%d] -> %s', return total end +-- When importing items into a locked chest, trash any remaining items if full +function Storage:trash(source, slot, count) + local trashcan = Util.find(self.remoteDefaults, 'mtype', 'trashcan') + if trashcan and trashcan.adapter and trashcan.adapter.online then + +_G._debug('TRA: %s[%d] (%d)', source or self.localName, slot, count or 64) + + return trashcan.adapter.pullItems(source or self.localName, slot, count) + end + return 0 +end + return Storage diff --git a/milo/apis/turtle/craft.lua b/milo/apis/turtle/craft.lua index c3e25ee..d27bc1b 100644 --- a/milo/apis/turtle/craft.lua +++ b/milo/apis/turtle/craft.lua @@ -11,7 +11,7 @@ local Craft = { STATUS_ERROR = 'error', STATUS_SUCCESS = 'success', - RECIPES_DIR = 'packages/core/etc/recipes', + RECIPES_DIR = 'packages/core/etc/recipes', USER_RECIPES = 'usr/config/recipes.db', MACHINE_LOOKUP = 'usr/config/machine_crafting.db', } @@ -94,7 +94,7 @@ local function machineCraft(recipe, storage, machineName, request, count, item) local xferred = { } for k,v in pairs(recipe.ingredients) do - local provided = storage:provide(splitKey(v), count, k, machineName) + local provided = storage:export(machineName, k, count, splitKey(v)) xferred[k] = { key = v, count = provided, @@ -125,8 +125,8 @@ local function turtleCraft(recipe, storage, request, count) for k,v in pairs(recipe.ingredients) do local item = splitKey(v) - if storage:provide(item, count, k) ~= count then - -- FIX: ingredients cannot be stacked + if storage:export(storage.localName, k, count, item) ~= count then + -- TODO: FIX: ingredients cannot be stacked request.status = 'unknown error' request.statusCode = Craft.STATUS_ERROR return @@ -405,38 +405,6 @@ function Craft.canCraft(item, count, items) return Craft.getCraftableAmount(Craft.recipes[item], count, items) == count end -function Craft.setRecipes(recipes) - Craft.recipes = recipes -end - -function Craft.getCraftableAmountTest() - local results = { } - Craft.setRecipes(Util.readTable('usr/etc/recipes.db')) - - local items = { - { name = 'minecraft:planks', damage = 0, count = 5 }, - { name = 'minecraft:log', damage = 0, count = 2 }, - } - results[1] = { item = 'chest', expected = 1, - got = Craft.getCraftableAmount(Craft.recipes['minecraft:chest:0'], 2, items) } - - items = { - { name = 'minecraft:log', damage = 0, count = 1 }, - { name = 'minecraft:coal', damage = 1, count = 1 }, - } - results[2] = { item = 'torch', expected = 4, - got = Craft.getCraftableAmount(Craft.recipes['minecraft:torch:0'], 4, items) } - - return results -end - -function Craft.craftRecipeTest(name, count) - local ChestAdapter = require('chestAdapter18') - local chestAdapter = ChestAdapter({ wrapSide = 'top', direction = 'down' }) - Craft.setRecipes(Util.readTable('usr/etc/recipes.db')) - return { Craft.craftRecipe(Craft.recipes[name], count, chestAdapter) } -end - Craft.loadRecipes() return Craft diff --git a/milo/plugins/craftTask.lua b/milo/plugins/craftTask.lua index 75711e0..2ccb73d 100644 --- a/milo/plugins/craftTask.lua +++ b/milo/plugins/craftTask.lua @@ -15,9 +15,11 @@ function craftTask:craft(recipe, item) if Milo:isCraftingPaused() then return end + + -- TODO: refactor into craft.lua Craft.processPending(item, context.storage) ---TODO: this needs to take into account what is pending + -- create a mini-list of items that are required for this recipe item.ingredients = Craft.getResourceList( recipe, Milo:listItems(), item.requested - item.crafted, item.pending) diff --git a/milo/plugins/listing.lua b/milo/plugins/listing.lua index d37e6ca..4f50832 100644 --- a/milo/plugins/listing.lua +++ b/milo/plugins/listing.lua @@ -6,18 +6,8 @@ local UI = require('ui') local Util = require('util') local colors = _G.colors -local os = _G.os - local context = Milo:getContext() --- TODO: fix -local function queue(fn) - while Milo:isCraftingPaused() do - os.sleep(1) - end - fn() -end - local function filterItems(t, filter, displayMode) if filter or displayMode > 0 then local r = { } @@ -61,7 +51,7 @@ local listingPage = UI.Page { }, statusBar = UI.StatusBar { filter = UI.TextEntry { - x = 1, ex = -13, + x = 1, ex = -17, limit = 50, shadowText = 'filter', shadowTextColor = colors.gray, @@ -71,12 +61,23 @@ local listingPage = UI.Page { [ 'enter' ] = 'craft', }, }, - storageStatus = UI.Button { - x = -12, ex = -4, - event = 'toggle_online', - backgroundColor = colors.cyan, + storageStatus = UI.Text { + x = -16, ex = -9, textColor = colors.lime, - text = '', + backgroundColor = colors.cyan, + value = '', + }, + amount = UI.TextEntry { + x = -8, ex = -4, + limit = 3, + shadowText = '1', + shadowTextColor = colors.gray, + backgroundColor = colors.black, + backgroundFocusColor = colors.black, + accelerators = { + [ 'enter' ] = 'eject_specified', + }, + help = 'Specify an amount to send', }, display = UI.Button { x = -3, @@ -95,7 +96,6 @@ local listingPage = UI.Page { [ 'control-a' ] = 'eject_all', [ 'control-m' ] = 'machines', - [ 'control-l' ] = 'resume', q = 'quit', }, @@ -132,25 +132,18 @@ function listingPage:eventHandler(event) if event.type == 'quit' then UI:exitPullEvents() - elseif event.type == 'resume' then - context.storage:setOnline(true) - elseif event.type == 'eject' then local item = self.grid:getSelected() if item then - queue(function() - item.count = Milo:craftAndEject(item, 1) - self.grid:draw() - end) + item.count = Milo:craftAndEject(item, 1) + self.grid:draw() end elseif event.type == 'eject_stack' then local item = self.grid:getSelected() if item then - queue(function() - item.count = Milo:craftAndEject(item, itemDB:getMaxCount(item)) - self.grid:draw() - end) + item.count = Milo:craftAndEject(item, itemDB:getMaxCount(item)) + self.grid:draw() end elseif event.type == 'eject_all' then @@ -158,10 +151,19 @@ function listingPage:eventHandler(event) if item then local updated = Milo:getItem(Milo:listItems(), item) if updated then - queue(function() Milo:eject(item, updated.count) end) + Milo:craftAndEject(item, updated.count) end end + elseif event.type == 'eject_specified' then + local item = self.grid:getSelected() + local count = tonumber(self.statusBar.amount.value) + if item and count then + self.statusBar.amount:reset() + self:setFocus(self.statusBar.filter) + Milo:craftAndEject(item, count) + end + elseif event.type == 'machines' then UI:setPage('machines') @@ -222,7 +224,7 @@ function listingPage:eventHandler(event) self.grid:draw() end - elseif event.type == 'text_change' then + elseif event.type == 'text_change' and event.element == self.statusBar.filter then self.filter = event.text if #self.filter == 0 then self.filter = nil @@ -240,7 +242,7 @@ end function listingPage:enable() self:refresh() self:setFocus(self.statusBar.filter) - self.timer = Event.onInterval(5, function() + self.timer = Event.onInterval(3, function() for _,v in pairs(self.allItems) do local c = context.storage.cache[v.key] v.count = c and c.count or 0 @@ -268,8 +270,8 @@ end Event.on({ 'storage_offline', 'storage_online' }, function(e, isOnline) -- TODO: Fix button - listingPage.statusBar.storageStatus.text = - isOnline and 'online' or 'offline' + listingPage.statusBar.storageStatus.value = + isOnline and '' or 'offline' listingPage.statusBar.storageStatus.textColor = isOnline and colors.lime or colors.red listingPage.statusBar.storageStatus:draw()