Draft: Store prototype API


  • Culture

    As both a mod developer and player I'm exited about this update, for modding, it was always annoying not being able to iterate over resources in a simple way. As a player, the entire store/carry/energyCapacity/carryCapacity mess was annoying, in some cases, I actually wrapped all those to make them match, very similar to how this is designed. It might seem a bit more clunky for stuff with only one resource, but simplifies more than enough other code to make it worth it.

    For those wanting to keep energy/energyCapacity, they can easily do a simple prototype extension in their code. Object.defineProperty(StructureExtension.prototype, 'energy', { get() { return this.store.energy } }) Object.defineProperty(StructureExtension.prototype, 'energyCapacity', { get() { return this.store.capacity } })



  • @ags131 yeah I've had to abstract around energy, store etc in various places (e.g. if (instanceof_energy_structure(struc)) { } else if (instanceof_store_structure(struc)) { } )

    Before I used typescript and enforced this at "compile-time", I remember fixing various bugs where I tried to treat an extension as if it had a store, or some other structure the other way around.

    I will be glad to have a unified interface, even though I'll have to at some point rework some of my code.

    It will make the game slightly less complex for new players.



  • From my point of view, I like and welcome this change, it will allow me to streamline my creep states so they can exit faster (Thus saving CPU.) I am concerned that heap use may increase, but that's Artch's problem... and in reality, any heap issue impacts everyone, mostly heavy users and users past the 300 CPU limit first, so I don't have a problem with that, despite that I'm breathing down the 300 CPU limit's neck. At the end of the day, it's pretty fair.

    Two potential issues. First, the obvious one: Nukers with their 5000 ghodium. Two stores? Ewww! Don't make them all the same except for this one weird bird. Applying a limit object with, for example: {G:5000,energy:250000} to it is the best solution, other structures would have null there to short circuit any checks.

    Then the 800 pound Gorilla: LABS. Labs are incompatible with a Store object unless you dynamically reset the limit object on the fly. Sure, the capacity remains 5000, but it can change from (for example.) {UO:3000,energy:2000} to {energy:2000} to {KH:3000,energy:2000}. It can have only one non-energy resource as it uses that to determine what it's doing.



  • I like the idea of a unified way of withdrawing / transferring resources, specially with getters that return 0 instead of undefined. I already have something like that in my code. I would have to keep it, though, since mine also supports pickup and drop. I'm not sure this change would benefit me specifically, but I see its value for general purpose code. The only things I don't like about this proposal are including nuker into this, which seems like a stretch and doesn't really add any value, and deprecating the old API. Forcing everybody to change their code, making it slightly more convoluted or it will eventually break, will just make people angry. I see no benefit in that. Keep both and everybody is happy.


  • YP

    I also like the new structure .. I already had added prototypes for some of the features, like a cached storeSum and carrySum and having it official is a plus. If the api is decided I'd like to add a polyfill so I can already start to use the new store object πŸ˜‰



  • I also welcome the API change but without deprecation of energy. If energy must be deprecated, then please make sure they are configurable to deactivate any warnings.

    I think professional players already know how to overwrite prototypes and have aliases and getters for their needs already. Everyone can already model a unified store, and properties for energy and whatever. Keeping that in mind, the API change is mainly for newbies and those who cannot change prototypes using typescript or have found ugly ways around the inconsistent API which they like to simplify.

    As a newbie I did often access creep.energy directly instead of creep.carry.energy. The same for storages, it was just so annoying that I always have to access either carry.energy or store.energy or energy when at first all I care about is just simple energy. The tower has no store, the creep has no energy and no store but carry instead; just not consistent. But I can make it consistent with prototypes. So yes, this change would not really help me, but it will help other newbies. One store for all kind of objects is a great change for the API.

    About the database change: Please just choose the most efficient model.

    I think every high-level player at 300 cpu cap and those who are looking forward to be one agrees with me that performance is more important than convenience. The database is hidden from us, so I prefer an efficient database model over a simple engine code. I have no idea if your proposed database change will have a positive or negative impact on overall performance, but I just fear that it's a negative impact because it looks more complex than before. In my eyes, the API and the database change are two different changes.

    In the end these proposed convenience changes should meet the following requirements:

    1. one store for every relevant object to allow a unified way to access energy and resources for convenience.
    2. It shall not increase overall tick rate, better decrease it.
    3. It shall not significantly decrease the free memory we can use
    4. It shall not increase our cpu consumption, better decrease it.

    If that's all met, then I believe every single player in the community will be happy about this change.


  • Dev Team

    @xenofix

    I have no idea if your proposed database change will have a positive or negative impact on overall performance, but I just fear that it's a negative impact because it looks more complex than before.

    It will be positive. Look at this engine code:

    exports.calcResources = function(object) {
        return _.sum(C.RESOURCES_ALL, i => typeof object[i] == 'object' ? object[i].amount : (object[i] || 0));
    };
    

    It will benefit greatly from this database change:

    exports.calcResources = function(object) {
        return _.sum(object.store);
    };
    

  • Dev Team

    @eduter

    Keep both and everybody is happy.

    This is exactly how legacy monsters are born πŸ‘Ύ


  • Dev Team

    @smokeman

    Then the 800 pound Gorilla: LABS. Labs are incompatible with a Store object unless you dynamically reset the limit object on the fly. Sure, the capacity remains 5000, but it can change from (for example.) {UO:3000,energy:2000} to {energy:2000} to {KH:3000,energy:2000}. It can have only one non-energy resource as it uses that to determine what it's doing.

    It will be ruled out by means of supportedResources that is set according to circumstances of the given lab.



  • For the nuker, you could have it charge instead of store-so the ghodium is lost when added to the nuker, instead of when it's used.

    Labs are trickier. Maybe add a .store.maxMineralType/.store.maxMineralAmount that returns the mineral with the highest count (not including energy, power, etc)? That way it would always work like .mineralType/.mineralAmount does now on labs, plus it would be useful for storage, terminals and creeps too.



  • Maybe a non-general-purpose and multiple-resource structure needs capacityFor of e.g. {G:5000,energy:250000}.

    capacity, getUsedCapacity() and getFreeCapacity() just makes sense for general-purpose (Storage, ...) and single-resource structures (Tower, Extension, ...). For all structures with mixed resources (powerSpawn, lab, nuke, ...) there needs to be more methods. Maybe getCapacityFor(resourceType), getUsedCapacityFor(resourceType), getFreeCapacityFor(resourceType). These can be implemented for general-purpose Store, too. So that the API for both types is the same.



  • I must say I'm for this change, and after a period of transition removing the old ones. It did confuse me a lot originally, and though I now have prototypes that handle most of it removing them will simplify things.

    As others have mentioned there needs to be some way to define the limits for Labs and Nuker, as others have mentioned a single store object would be my preference with some kind of per resource limit field. The other "important" thing I do a lot is to check what resource type is in a lab, at the moment this is a simple .mineralType. I'd rather not have to iterate over the store / supportedResources and ignore energy to be able to get it in the new way of doing things (though if it were the only way I could add a prototype of .mineralType which did it and cached the result).

    ☝


  • I really like the unification of carry, store and energy as well as the inclusion of total and free methods.

    The beauty of unified store is marred by StructureNuker and StructureLab, we need a little more consideration here.

    I think everyone dislikes separate properties. I think @Xenofix's idea of extending the Store interface deal with the two oddballs is best.

    How about extending the getUsedCapacity and getFreeCapacity to accept an optional resource type argument and adding a getTotalCapacity.

    That way I can easily write uniform transfer and withdraw code by always including the resource type.

    nuker.store.getTotalCapacity(RESOURCE_GHODIUM) => 5000
    nuker.store.getTotalCapacity(RESOURCE_ENERGY) => 300000
    nuker.store.getTotalCapacity() => 305000 // or 0 or whatever make sense
    creep.store.getUsedCapacity(/*anything*/) => the same value
    
    // look how nice labs work out.
    lab.store.getUsedCapacity(RESOURCE_UTRIUM) => 1000
    lab.store.getTotalCapacity(RESOURCE_UTRIUM) => 3000
    lab.store.getTotalCapacity(RESOURCE_KEANIUM) => 0
    
    Store.capacity === Store.getTotalCapacity()
    Store[RESOURCE_ZYNTHIUM] === Store.getUsedCapacity(RESOURCE_ZYNTHIUM)
    

    I would never need to use Store.supportedResources since getTotalCapacity answers that same question, and for the most part supportedResource is statically known based on structureType.

    I think we'll still want a .mineralType member on the lab, but that could just be implemented with getUsedCapacity.



  • IMO incoming:

    This change is long overdue, seriously but a couple of things:
    For God's sake use "storage" or "stored", "store" is a incorrect definition and prone for misunderstandings.
    It maybe it should be super class for all entities that contain something so that it is possible to directly call withdraw on them without the need to punch .store/storage/stored. between object and call.
    This way all resource handling can be done central in one class without the need to fiddle in the endusers object code.

    I have a lot more in mind but I'm tired so that will it be for now.


  • YP

    @artch would it be possible from the users script to modify the storage object of a creep and reset the cache? That would be nice because I currently already patch the creep object for example on successful withdraws, pickups and stuff like that so decisions made after that are based of the new values ( I know those can still fail when multiple creeps for example withdraw, so that should not be automatic maybe ) but I use it only for the decision to move to the creeps next target for example in the same tick.

    If this is a breaking change ( and there are already other mentions in the docs about deprecated stuff like the transfer method on structures, the old spawn function,... ) maybe it's time to create a second selectable runtime? I don't know how much work that would be but perhaps it would also allow us to get one with lodash 4 already included ? πŸ˜‰


  • Dev Team

    @w4rl0ck Properties will be set with configurable:true, so yes, you can modify them.


  • Dev Team

    @deft-code I like this idea! Although, I would rename getTotalCapacity to getCapacity and remove the .capacity property at all then. The top post updated, check it out!

    πŸ‘


  • I still don't like it to be called Store since it is used a lot in other contexts.
    Hence I like to propose to use a term that makes it very clear we talk about non stationary stuff:
    Cargo <- This is short, pregnant and definitive.

    If you think I'm nitpicking, think again.
    Clear terminology is key for a good and comprehensible Codebase that is supposed to be used by others.


  • Dev Team

    @mrfaul Are you a native English speaker? I'm not, so I use dictionaries a lot. This is what the Oxford Dictionary gives for "store":

    A quantity or supply of something kept for use as needed.

    β€˜the squirrel has a store of food’

    Looks valid to me.



  • I'm not a native speaker and butcher the grammar frequently but German is really not far from English.
    (for those native tongues who learning German and cursing me right now for that statement, sry for us the complexity goes down πŸ˜…)

    This is not about its definition, it is about distinction in context.
    I have a B.E. in machine design and this is one of the biggest challenge in my profession.
    German by its nature is a very precise language, this keeps a lot of potential misunderstandings in check since we have a word for almost everything.
    However when translating this in English, a lot (I mean a shit load) of this precise information is lost since English simplifies and reuses a lot of words to a degree that they are able to have very different meanings.

    To combat this we have to make sure to use distinctive terminology in international conversations.
    Now "Store" and "Cargo" are very similar with the distinction that "Cargo" is exclusively used for "stuff that is being transported"
    This is the better term in this case since it will be used by structures and creeps.
    Decide for your self what is easier to distinguish in a non written conversation or when you are hunting for bugs which may include typos:
    Storage.store.[RESOURCE] or Storage.cargo[RESOURCE]

    "store" is a completely valid term but since it is already used it shouldn't be given a additional meaning.

    P.S. And don't get me started on the difference between "american" and "british" English that deserves it own rant πŸ˜‰


Locked