Add functionality to memory objects



  • I have a class with some properties and methods that will eventually be dropped into a room memory.

    On the next tick when I pull that memory object I have all the correct properties but non of the methods. I understand why that is and that isn't too big of a problem. I'll show my problem in the below code:

    class testClass {
        constructor(){
           this.prop = 'test';
        }
    
        test(){
            return this.prop;
        }
    }
    

    Later I will save this object:

    var room = Game.rooms['sim'];
    var classObj = new testClass();
    room.memory.test = classObj;
    

    On the next tick I will pull up the object:

    var room = Game.rooms['sim'];
    var classObj = room.memory.test;
    //correctly sends the word test to the console. 
    console.log(classObj.prop);
    //understandably sends undefined to the console.
    console.log(classObj.test);
    

    I have attempted to remedy this problem by adding the function to the memory object prototype.

     Object.getPrototypeOf(classObj).test = testClass.prototype.test;
    

    This works some of the time except I constantly keep getting this error in the console every couple ticks before my whole script is even started.

    "Error: "test" is not a valid memory segment ID at self.onmessage (blob:https://screeps.com/83fe0913-2d1b-43e0-83a7-d3490fc5868b:2:100470)"

    Am I doing this completely wrong? What can I do to make this error go away?


  • TMB

    I haven't touched these kind of things yet and I don't know what is wrong here, but there's one thing I'm thinking about :
    Why copy the properties from one prototype onto the other? What happens if you try to assign the whole prototype directly ?

    Object.setPrototypeOf(classObj, testClass.prototype)
    


  • Hmmm never tried that.

    Turns out the exact same thing happens.



  • Ok so I've been taking a look at this a little more and it seems like what I'm trying to is legal. I will put this in the bug section and see what happens there.


  • Dev Team

    @grandpa_sam No this is not legal, Memory is for storing plain data only. setPrototypeOf should be the solution.



  • As I said before. setPrototypeOf has the exact same problem with the exact error.

    Also from what I've read and tested, directly adding a function to an objects prototype would not be saved because JSON.stringify ignores functions all together


  • Dev Team

    Works fine for me.

    JSON.stringify(Object.setPrototypeOf(JSON.parse(JSON.stringify(new RoomPosition(28,36,'W23S82'))),RoomPosition.prototype).look())
    

    Here what we do here: new RoomPosition(28,36,'W23S82') creates an object with methods and everything, then

    JSON.stringify makes a serialized data, then

    JSON.parse makes plain data object (the exact same thing you get at next tick if you store it in memory), then

    Object.setPrototypeOf makes the object great again a valid RoomPosition object, then

    I test it with RoomPosition.look() method.

    Result:

    [
      {
        "type": "structure",
        "structure": {
          "room": {
            "name": "W23S82",
            "energyAvailable": 2300,
            "energyCapacityAvailable": 2300,
            "visual": {
              "roomName": "W23S82"
            }
          },
          "pos": {
            "x": 28,
            "y": 36,
            "roomName": "W23S82"
          },
          "id": "5bd465b4561835306e59ff9b",
          "store": {
            "energy": 153780,
            "XKH2O": 79728,
            "XZHO2": 19849
          },
          "storeCapacity": 1000000,
          "owner": {
            "username": "o4kapuk"
          },
          "my": true,
          "hits": 10000,
          "hitsMax": 10000,
          "structureType": "storage"
        }
      },
      {
        "type": "structure",
        "structure": {
          "room": {
            "name": "W23S82",
            "energyAvailable": 2300,
            "energyCapacityAvailable": 2300,
            "visual": {
              "roomName": "W23S82"
            }
          },
          "pos": {
            "x": 28,
            "y": 36,
            "roomName": "W23S82"
          },
          "id": "5bd709123bdc376787ae3146",
          "ticksToDecay": 14,
          "isPublic": false,
          "owner": {
            "username": "o4kapuk"
          },
          "my": true,
          "hits": 794701,
          "hitsMax": 30000000,
          "structureType": "rampart"
        }
      },
      {
        "type": "terrain",
        "terrain": "plain"
      }
    ]
    

    UPD While you can store objects to memory and restore them from there, you're not supposed to do that. I'm serious, don't do that. This is bad. You're better than this. Make it stop...



  • Ok cool. Thanks for your help. I must be doing something wrong with this. I will get back here with more information.

    EDIT: also clever joke. When I re read it I laughed

    ☺


  • Ok I have a question on your edit. Are you saying that while technically Object.setPrototypeOf would fix my problem, I shouldn't use it and instead should try a different line of thinking?

    I'm not at all trying to save anything but the data inside the objects but I would like to be able to keep my key functionality pertaining to the data in those objects in one place preferably nicely wrapped up in the object prototype itself.

    I guess my goal here is when its time to save to memory I rip all the data out of the class. Then on the next tick when its time to load I put that data back into a new instance of the same class made that tick.


  • Dev Team

    Well, I personally solved it with wrappers. Basically, I create a plain data object like this:

    {
      "id": "75912f1f-dd4d-4665-9ae7-058fc12710cd",
      "priority": 15,
      "subtasks": [],
      "lootObjectId": "5b78ab2cd6d21b3580d38768",
      "position": 1095405321,
      "type": "lootResource",
      "isCompeted": false
    }
    

    Then, when I need to work with this data in object-oriented way, I create class passing reference to the object to its constructor. The class saves this reference and work with these data directly like this:

    getPriority: function() { return this._data["priority"]; }
    setPriority: function(newValue) { this._data["priority"] = newValue; }
    getLookObject: function() { return Game.getObjectById(this._data["lootObjectId"]); }
    

    And so on. This way, I don't need to bother about prototypes or something, plus I don't have to explicitly 'save' data from work objects to a plain data object; also work objects may be efficiently cached in heap, again, without any dancing around data updates.

    Of course, this is not the single best way of doing things, custom serializers may be better (noticed "position" property?).



  • Ok great,

    Thanks for having this discussion. I'm at work so I can't test this stuff but you have given me multiple paths to explore so I'm going mark this as resolved.

    Thanks again for your help.


  • Dev Team

    @grandpa_sam You welcome 🙂


  • TMB

    As a sidenote, I believe you can call a function onto objects it's not a part of. Here's an example with my Species class when reviving a deceased creep :

    let species = Memory.species[creep.memory.species];
    let body = SpeciesClass.prototype.findByCost.call(species, energyAvailable);
    

    That last line would work the same as if species were an instance of SpeciesClass and I wrote :

    let body = species.findByCost(energyAvailable);
    

    (Though to be honnest it's part of a module I haven't fully tested yet)

    ☝


  • Thanks for the info Estecka. I will look into that as well.


  • TMB

    However watch out for nested function calls. In my example, if findByCost tried internally to call more functions from SpeciesClass with the usual this.function() syntax then it will fail.
    For every functions that you plan to call onto "foreign" objects, make sure all nested methods are called using the YourClass.prototype.function.call(this) syntax.

    ☝