Clone on import

Hi,

I have been saving / loading my own custom classes to .j3o format using the Savable interface. Everything was fine until I wanted multiple instances of the same object to be loaded.

When I load the same object, i do not get a clone, I get the same instance returned. I assume this is because the assetmanager has cashed the instance and is simply returning a reference to the first instance loaded. But how what is the standard approach to create a new instance / clone? I assumed there is a smarter way than simply writing up .clone methods for all my savable classes? Or a method that integrates with the assetmanager.

Cheers,

NiHal

What you say is not exactly true. If e.g. you save a Custom Control with a model you get an instance for each model, you can exactly control the behavior though the cloneForSpatial method too. What do you try to do exactly? j3o files should not just contain any savable btw, they should contain a spatial, use another suffix for your own Savables to avoid issues.

1 Like

Where are these custom classes stored? You must make sure to implement the clone() method correctly, if you’re extending Spatial or subclasses. By default clone() will only copy the reference to the object, but not the object itself. If you’re using Controls, then you have to implement cloneForSpatial() like Normen mentioned.

1 Like

And btw …if you extend Node, Spatial or Geometry and try to save that… You do it wrong :wink: Move the code to Contols.

@normen said: What you say is not exactly true. If e.g. you save a Custom Control with a model you get an instance for each model, you can exactly control the behavior though the cloneForSpatial method too. What do you try to do exactly? j3o files should not just contain any savable btw, they should contain a spatial, use another suffix for your own Savables to avoid issues.

Oh, actually I am not loading saving spatials. The class I am storing contains meta-data that my game uses to generate the geometry and associated properties.

@Momoko_Fan said: Where are these custom classes stored? You must make sure to implement the clone() method correctly, if you're extending Spatial or subclasses. By default clone() will only copy the reference to the object, but not the object itself. If you're using Controls, then you have to implement cloneForSpatial() like Normen mentioned.

I store the classes in the users home directory. I have a file structure that goes something like ‘user.home’/Game/resources/object.j3o for general game resources and ‘user.home’/Game/savegames/savegameName/… for save game specific resources.

Sorry about the confusion. I though .j3o could be used for exporting / importing any resource. It works like a charm though… No issues, besides the cloning issue stuff. So I guess I need to register a custom loader or something to change the extention name to something like .metadata? Sorry, I would post some code, but I am at work atm.

Just register a BinaryLoader with your extension, yes. Like the filter post processor j3f files.

@normen said: Just register a BinaryLoader with your extension, yes. Like the filter post processor j3f files.

Hi, I browsed around. I assume you mean register an AssetLoader? I have rewritten my implementation, I am now importing / exporting objects with a custom extension “.so”.

I have written a Savable “SavableObject” that needs to be imported / exported. An AssetLoader “SavableObjectLoader”, that uses a BinaryImporter to load the object. Besides being able to use custom extentions, it has not solved any issues, as importing the same object twice still only returns a reference to the same object… I can see that the AssetLoader.load( … ) is not called the second time I load an object with the same assetKey. I think AssetManager.loadAsset( … ).clone() would be a strange method to import the same resource twice…

Here is my code:
[java]

import com.jme3.app.SimpleApplication;
import com.jme3.asset.plugins.FileLocator;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
import com.jme3.export.binary.BinaryExporter;
import com.jme3.export.binary.BinaryImporter;
import com.jme3.scene.Spatial;
import com.jme3.system.JmeContext;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Main extends SimpleApplication {

/**
 * The savable object that needs import / export
 */
public static class SavableObject implements Savable {

    float value = 7f;
    int no = 5;
    
    public SavableObject() { }
    
    public SavableObject( float value, int no ) {
        this.value = value;
        this.no = no;
    }
    
    public void write( JmeExporter ex ) throws IOException {
        OutputCapsule c = ex.getCapsule( this );
        c.write( value, "value", 0f );
        c.write( no, "no", 0 );
    }

    public void read( JmeImporter im ) throws IOException {
        InputCapsule c = im.getCapsule( this );
        this.value = c.readFloat( "value", 0f );
        this.no = c.readInt( "no", 0 );
        Spatial s;
    }

}

private static String gameName = "TestLoadSave";

public static void main(String[] args) {
    Logger.getLogger("").setLevel( Level.WARNING );
    Main app = new Main();
    app.start( JmeContext.Type.Headless );
}

@Override
public void simpleInitApp() {

    // Register a locator for our game resources:
    String userHome = System.getProperty( "user.home" ) + "\\" + gameName + "\\";
    this.getAssetManager().registerLocator( userHome , FileLocator.class );

    // Register a loader for loading SavableObject.class
    this.getAssetManager().registerLoader( BinaryImporter.class, "so" );
    
    // Save objects:
    this.testSave();
    
    // Load objects:
    this.testLoad();
    
    System.exit( 0 );
    
}

private void testSave(){
    this.exportSavableObject( new SavableObject( 7.1f, 0 ), "no0" );
    this.exportSavableObject( new SavableObject( 7.2f, 1 ), "no1" );
    this.exportSavableObject( new SavableObject( 7.3f, 2 ), "no2" );
}

private void testLoad(){
    SavableObject s0 = this.importSavableObject( "so\\no0.so" );
    SavableObject s1 = this.importSavableObject( "so\\no1.so" );
    SavableObject s2 = this.importSavableObject( "so\\no2.so" );
    SavableObject s3 = this.importSavableObject( "so\\no2.so" );
    System.out.println("s2 and s3 is the same object: " + ( s2 == s3 ) );
    
    Spatial tea1 = this.assetManager.loadModel("Models/Teapot/Teapot.obj");
    Spatial tea2 = this.assetManager.loadModel("Models/Teapot/Teapot.obj");
    System.out.println("tea1 and tea2 is the same object: " + ( tea1 == tea2 ) );
    
}

private SavableObject importSavableObject( String path ){
    SavableObject so = ( SavableObject ) this.getAssetManager().loadAsset( path );
    System.out.println("Loaded object #" + so.no + " result: " + so );
    return so;
}

private void exportSavableObject( SavableObject toSave, String name ) {
    String userHome = System.getProperty( "user.home" )+ "\\" + gameName + "\\";
    BinaryExporter exporter = BinaryExporter.getInstance();
    File file = new File( userHome +  "so\\" + name + ".so"  );
    
    System.out.print("Saving SavableObject to: " + file.getAbsolutePath() );
    try {
        exporter.save( toSave, file ); 
        System.out.println( " - Success!" );
    } catch ( IOException ex ) {
        System.out.println( " - Failure!" );
        ex.printStackTrace();
    }
}

}

[/java]

No, thats wrong, no need to create that Loader and definitely don’t use BinaryImporter directly! :slight_smile:
You only need to register a normal BinaryImporter with your suffix:
assetManager.registerLoader(com.jme3.export.binary.BinaryImporter.class, “so”);
Then it will work.

1 Like

Thank you, that does shorten down my code, and removes the need to implement the AssetLoader. But the issue remains that the loaded objects are always just references to the first loaded object. So running my code will yield the following output:

This:
[java]
SavableObject s0 = this.importSavableObject( “so\no0.so” );
SavableObject s1 = this.importSavableObject( “so\no1.so” );
SavableObject s2 = this.importSavableObject( “so\no2.so” );
SavableObject s3 = this.importSavableObject( “so\no2.so” );
System.out.println("s2 and s3 is the same object: " + ( s2 == s3 ) );

    Spatial tea1 = this.assetManager.loadModel("Models/Teapot/Teapot.obj");
    Spatial tea2 = this.assetManager.loadModel("Models/Teapot/Teapot.obj");
    System.out.println("tea1 and tea2 is the same object: " + ( tea1 == tea2 ) );

[/java]
resulting in this:

Loaded object #0 result: mygame.Main$SavableObject@1037c71
Loaded object #1 result: mygame.Main$SavableObject@1df073d
Loaded object #2 result: mygame.Main$SavableObject@1546e25
Loaded object #2 result: mygame.Main$SavableObject@1546e25 <- Same memory address
s2 and s3 is the same object: true <- Expected a false
tea1 and tea2 is the same object: false <- As expected

As you can see loading object #2 twice, returns a reference to the same object. If I loaded the same mesh twice using AssetManager.loadModel( … ), I believe I would get two separate instances of the spatial… That is the behaviour I am trying to achieve.

I have updated my code posted above.

Hm, thats seems indeed strange… Now that you got all that code together, can you wrap that up in a test case so we can look into it please? :slight_smile:

@normen said: Hm, thats seems indeed strange.. Now that you got all that code together, can you wrap that up in a test case so we can look into it please? :)

I am unsure if anything further is required to call this a test case ( it is the code I posted earlier, it should run on any machine - it now requires the JME3-testdata.jar ). I have added the proof that loading a .obj file produces two separate instances, while loading my .so file produces only one instance.

Should I wrap it up in a selfcontained .zip?

[java]
package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.asset.plugins.FileLocator;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
import com.jme3.export.binary.BinaryExporter;
import com.jme3.export.binary.BinaryImporter;
import com.jme3.scene.Spatial;
import com.jme3.system.JmeContext;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Main extends SimpleApplication {

/**
 * The savable object that needs import / export
 */
public static class SavableObject implements Savable {

    float value = 7f;
    int no = 5;
    
    public SavableObject() { }
    
    public SavableObject( float value, int no ) {
        this.value = value;
        this.no = no;
    }
    
    public void write( JmeExporter ex ) throws IOException {
        OutputCapsule c = ex.getCapsule( this );
        c.write( value, "value", 0f );
        c.write( no, "no", 0 );
    }

    public void read( JmeImporter im ) throws IOException {
        InputCapsule c = im.getCapsule( this );
        this.value = c.readFloat( "value", 0f );
        this.no = c.readInt( "no", 0 );
        Spatial s;
    }

}

private static String gameName = "TestLoadSave";

public static void main(String[] args) {
    Logger.getLogger("").setLevel( Level.WARNING );
    Main app = new Main();
    app.start( JmeContext.Type.Headless );
}

@Override
public void simpleInitApp() {

    // Register a locator for our game resources:
    String userHome = System.getProperty( "user.home" ) + "\\" + gameName + "\\";
    this.getAssetManager().registerLocator( userHome , FileLocator.class );

    // Register a loader for loading SavableObject.class
    this.getAssetManager().registerLoader( BinaryImporter.class, "so" );
    
    // Save objects:
    this.testSave();
    
    // Load objects:
    this.testLoad();
    
    System.exit( 0 );
    
}

private void testSave(){
    this.exportSavableObject( new SavableObject( 7.1f, 0 ), "no0" );
    this.exportSavableObject( new SavableObject( 7.2f, 1 ), "no1" );
    this.exportSavableObject( new SavableObject( 7.3f, 2 ), "no2" );
}

private void testLoad(){
    SavableObject s0 = this.importSavableObject( "so\\no0.so" );
    SavableObject s1 = this.importSavableObject( "so\\no1.so" );
    SavableObject s2 = this.importSavableObject( "so\\no2.so" );
    SavableObject s3 = this.importSavableObject( "so\\no2.so" );
    System.out.println("s2 and s3 is the same object: " + ( s2 == s3 ) );
    
    Spatial tea1 = this.assetManager.loadModel("Models/Teapot/Teapot.obj");
    Spatial tea2 = this.assetManager.loadModel("Models/Teapot/Teapot.obj");
    System.out.println("tea1 and tea2 is the same object: " + ( tea1 == tea2 ) );
    
}

private SavableObject importSavableObject( String path ){
    SavableObject so = ( SavableObject ) this.getAssetManager().loadAsset( path );
    System.out.println("Loaded object #" + so.no + " result: " + so );
    return so;
}

private void exportSavableObject( SavableObject toSave, String name ) {
    String userHome = System.getProperty( "user.home" )+ "\\" + gameName + "\\";
    BinaryExporter exporter = BinaryExporter.getInstance();
    File file = new File( userHome +  "so\\" + name + ".so"  );
    
    System.out.print("Saving SavableObject to: " + file.getAbsolutePath() );
    try {
        exporter.save( toSave, file ); 
        System.out.println( " - Success!" );
    } catch ( IOException ex ) {
        System.out.println( " - Failure!" );
        ex.printStackTrace();
    }
}

}
[/java]

2 Likes

Thanks, the general serialization is a base system that the cloning stuff builds upon, the (de)serialization should only create references when these were actual references at the time the objects were serialized. @Momoko_Fan? Any Ideas?

I looked at the DesktopAssetManager.loadAsset( … ) implementation. According to that, it will produce a clone using an AssetProcessor if the Savable implements ClonableSmartAsset and has an AssetProcessor associated.

Shorter version of DesktopAssetManager.loadAsset( … )
[java]
/**
* Thread-safe.
*
* @param
* @param key
* @return the loaded asset
*/
public T loadAsset(AssetKey key){

// Some code removed

    AssetCache cache = handler.getCache(key.getCacheType());
    AssetProcessor proc = handler.getProcessor(key.getProcessorType());
    
    Object obj = cache != null ? cache.getFromCache(key) : null;
    if (obj == null){

// Load the asset if it does not exist in the cache
}

// The following handles cloning, but only if object implements ClonableSmartAsset and an AssetProcessor exists…
T clone = (T) obj;
if (clone instanceof CloneableSmartAsset){
if (proc == null){
throw new IllegalStateException("Asset implements "
+ "CloneableSmartAsset but doesn’t "
+ “have processor to handle cloning”);
}else{
clone = (T) proc.createClone(obj);
if (cache != null && clone != obj){
cache.registerAssetClone(key, clone);
} else{
throw new IllegalStateException("Asset implements "
+ "CloneableSmartAsset but doesn’t have cache or "
+ “was not cloned”);
}
}
}

    return clone;
}

[/java]

But from what I can see in the DesktopAssetManager there is no way to associate an AssetProcessor…? Am I wrong?

And is that the correct approach? Make all Savables that requires multiple instances to:

  • Implement Savable, ClonableSmartAsset
  • register a Loader
  • register an AssetProcessor ( including full .clone functionality )
    Is that the correct approach? I was hoping I would not need to implement AssetProcessors and cloning. Guess I was sort of hoping that the AssetManager would just cache the binary data and rebuild another instance based on the bit stream, saving me of alot of work.

You don’t need to register the AssetProcessor or CloneableSmartAsset normally, that should be the special case. I guess that came in with some of the latest changes, lets wait for the creators opinion.

@normen said: You don't need to register the AssetProcessor or CloneableSmartAsset normally, that should be the special case. I guess that came in with some of the latest changes, lets wait for the creators opinion.

Hmm, okay. Guess I am stuck here then, until a solution comes around. Is there a way I can circumvent this issue for now? Like deleting the cached version of a loaded object or deactivating caching while loading specific files? I cannot see any. If not, I guess I will just subdue the AssetManager with ugly hacks… :stuck_out_tongue:

@nihal said: Hmm, okay. Guess I am stuck here then, until a solution comes around. Is there a way I can circumvent this issue for now? Like deleting the cached version of a loaded object or deactivating caching while loading specific files? I cannot see any. If not, I guess I will just subdue the AssetManager with ugly hacks.... :p
Just load it from the classpath? Anyway if you didn't understand this yet, this is how "working on the issue" looks. You can't expect your problem to be solved the next day just because you told somebody about it. I told you everything I know.
@normen said: Just load it from the classpath? Anyway if you didn't understand this yet, this is how "working on the issue" looks. You can't expect your problem to be solved the next day just because you told somebody about it. I told you everything I know.

Sorry, I was not expecting a next day solution, which is why I was looking for suggestions for a work around. I really appreciate that you have taken your time to look into this issue, even without solving my initial issue, you have helped improve my load/save functionality a lot from what I initially had.

I’ll go work on a solution and stop pushing you guys. If I come across a good general solutions and not just a hack, I’ll post it here.

Cheers,

NiHal

1 Like

If you want to use the AssetManager with cloning you have to implement CloneableSmartAsset and load it with an AssetKey that is configured with the proper cache and processor … This is the only means by which the AssetManager can keep track of the clones and properly remove the asset from the cache when no clones of it are present anymore and thus it isn’t used.
E.g. the AssetKey subclass should have this code:

[java]@Override
public Class<? extends AssetCache> getCacheType(){
return WeakRefCloneAssetCache.class;
}

@Override
public Class<? extends AssetProcessor> getProcessorType(){
    return CloneableAssetProcessor.class;
}[/java]

and the asset object itself should have key variable that can be modified from the methods setKey/getKey from CloneableSmartAsset interface.

1 Like

Thank you @Momoko_Fan, I have sorted this out now.

I have not seen this documented anywhere but in the java doc, where you need to know that you are looking for subclasses of AssetCache and AssetProcessor, these subclasses explain their specializations quite well. I am leaving this code example for any future monkeys, it has an example of a clone-on-load object and a reference-on-load object, using one object that is a ClonableSmartAsset and one that is not. There is plenty of System.out.println, to provide quick transparency of de-serialization, object creation and cloning.

[java]
package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.asset.AssetKey;
import com.jme3.asset.AssetProcessor;
import com.jme3.asset.CloneableAssetProcessor;
import com.jme3.asset.CloneableSmartAsset;
import com.jme3.asset.cache.AssetCache;
import com.jme3.asset.cache.WeakRefAssetCache;
import com.jme3.asset.cache.WeakRefCloneAssetCache;
import com.jme3.asset.plugins.FileLocator;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
import com.jme3.export.binary.BinaryExporter;
import com.jme3.export.binary.BinaryImporter;
import com.jme3.system.JmeContext;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Main extends SimpleApplication {

/**
 * The savable object that needs import / export
 */
public static class ClonableSavable implements Savable, CloneableSmartAsset {

    private AssetKey key;
    
    float value = 7f;
    int no = 5;
    
    public ClonableSavable() { 
        System.out.println(" - Create ClonableSavable() - ( de-serialization )" );
    }
    
    public ClonableSavable( float value, int no ) {
        this.value = value;
        this.no = no;
        System.out.println(" - Create ClonableSavable( " + value +", " + no + " ) - ( creation || clone )" );
    }
    
    public void write( JmeExporter ex ) throws IOException {
        OutputCapsule c = ex.getCapsule( this );
        c.write( value, "value", 0f );
        c.write( no, "no", 0 );
    }

    public void read( JmeImporter im ) throws IOException {
        InputCapsule c = im.getCapsule( this );
        this.value = c.readFloat( "value", 0f );
        this.no = c.readInt( "no", 0 );
        System.out.println(" - Reading ClonableSavable()" );
    }

    public void setKey( AssetKey key ) {
        this.key = key;
    }

    public AssetKey getKey() {
        return this.key;
    }

    @Override
    public Object clone() {
        ClonableSavable clone = new ClonableSavable( this.value, this.no );
        System.out.println(" - Cloning class: " + this.getClass().getSimpleName() + ", object: @" + this.hashCode() + ", clone: @" + clone.hashCode() );
        return clone;
    }
    
}

public static class NonClonableSavable implements Savable {

    float value = 7f;
    int no = 5;
    
    public NonClonableSavable() { 
        System.out.println(" - Create NonClonableSavable() - ( de-serialization )" );
    }
    
    public NonClonableSavable( float value, int no ) {
        this.value = value;
        this.no = no;
        System.out.println(" - Create NonClonableSavable( " + value +", " + no + " ) - ( creation )" );
    }
    
    public void write( JmeExporter ex ) throws IOException {
        OutputCapsule c = ex.getCapsule( this );
        c.write( value, "value", 0f );
        c.write( no, "no", 0 );
    }

    public void read( JmeImporter im ) throws IOException {
        InputCapsule c = im.getCapsule( this );
        this.value = c.readFloat( "value", 0f );
        this.no = c.readInt( "no", 0 );
        System.out.println(" - Reading NonClonableSavable()" );
    }
    
    Object o;

}    

private static String gameName = "TestLoadSave";

public static void main(String[] args) {
    Logger.getLogger("").setLevel( Level.WARNING );
    Main app = new Main();
    app.start( JmeContext.Type.Headless );
}

@Override
public void simpleInitApp() {

    // Register a locator for our game resources:
    String userHome = System.getProperty( "user.home" ) + "\\" + gameName + "\\";
    this.getAssetManager().registerLocator( userHome , FileLocator.class );

    // Register a loader for loading SavableObject.class
    this.getAssetManager().registerLoader( BinaryImporter.class, "so1" );
    this.getAssetManager().registerLoader( BinaryImporter.class, "so2" );
    
    // Save objects:
    this.testSave();
    
    // Load objects:
    this.testLoad();
    
    System.exit( 0 );
    
}

private void testSave(){
    System.out.println("\n****************");
    System.out.println("**   SAVING   **");
    System.out.println("****************");
    this.exportSavableObject( new ClonableSavable( 7.1f, 0 ), "test\\so.so1" );
    this.exportSavableObject( new NonClonableSavable( 7.2f, 1 ), "test\\so.so2" );
}

private void testLoad(){

    System.out.println("\n*****************");
    System.out.println("**   LOADING   **");
    System.out.println("*****************");
    
    String stringKey = "test\\so.so2";
    AssetKey noCloneKey = new AssetKey( "test\\so.so2" );
    
    AssetKey cloneKey = new AssetKey( "test\\so.so1" ){
        @Override
        public Class getCacheType(){
            return WeakRefCloneAssetCache.class;
        }
        @Override
        public Class getProcessorType(){
            return CloneableAssetProcessor.class;
        }        
    };

    System.out.println("Loading NonClonableSavable #1");
    NonClonableSavable s1_no1 = ( NonClonableSavable ) this.getAssetManager().loadAsset( noCloneKey );
    System.out.println("Loading NonClonableSavable #2");
    NonClonableSavable s1_no2 = ( NonClonableSavable ) this.getAssetManager().loadAsset( noCloneKey );

    System.out.println("Loading ClonableSavable #1");
    ClonableSavable s2_no1 = ( ClonableSavable ) this.getAssetManager().loadAsset( cloneKey );
    System.out.println("Loading ClonableSavable #2");
    ClonableSavable s2_no2 = ( ClonableSavable ) this.getAssetManager().loadAsset( cloneKey );
    
    System.out.println("\n*****************");
    System.out.println("**   RESULTS   **");
    System.out.println("*****************");
    System.out.println( s1_no1.getClass().getSimpleName() + " loaded using noCloneKey:");
    System.out.println("   no1 == no2: " + ( s1_no1 == s1_no2 ) + " ( @"+ s1_no1.hashCode() + " == @" + s1_no2.hashCode() +" ) - " + ( ( s1_no1 == s1_no2 ) ? "REFERENCE!" : "CLONES!" ) );
    System.out.println( s2_no1.getClass().getSimpleName() +" loaded using cloneKey:");
    System.out.println("   no1 == no2: " + ( s2_no1 == s2_no2 ) + " ( @"+ s2_no1.hashCode() + " == @" + s2_no2.hashCode() +" ) - "  + ( ( s2_no1 == s2_no2 ) ? "REFERENCE!" : "CLONES!" ) );
    
}

private ClonableSavable importSavableObject( AssetKey key ){
    ClonableSavable so = ( ClonableSavable ) this.getAssetManager().loadAsset( key );
    System.out.println("Loaded object #" + so.no + " result: " + so );
    return so;
}

private void exportSavableObject( Savable toSave, String name ) {
    String userHome = System.getProperty( "user.home" )+ "\\" + gameName + "\\";
    BinaryExporter exporter = BinaryExporter.getInstance();
    File file = new File( userHome + name  );
    
    System.out.print("Saving SavableObject to: " + file.getAbsolutePath() );
    try {
        exporter.save( toSave, file ); 
        System.out.println( " - Success!" );
    } catch ( IOException ex ) {
        System.out.println( " - Failure!" );
        ex.printStackTrace();
    }
}

}
[/java]

Output should look like this:


** SAVING **


  • Create ClonableSavable( 7.1, 0 ) - ( creation || clone )
    Saving SavableObject to: C:\Users\nhald\TestLoadSave\test\so.so1 - Success!
  • Create NonClonableSavable( 7.2, 1 ) - ( creation )
    Saving SavableObject to: C:\Users\nhald\TestLoadSave\test\so.so2 - Success!

** LOADING **


Loading NonClonableSavable #1

  • Create NonClonableSavable() - ( de-serialization )
  • Reading NonClonableSavable()
    Loading NonClonableSavable #2
    Loading ClonableSavable #1
  • Create ClonableSavable() - ( de-serialization )
  • Reading ClonableSavable()
  • Create ClonableSavable( 7.1, 0 ) - ( creation || clone )
  • Cloning class: ClonableSavable, object: @26094370, clone: @11650554
    Loading ClonableSavable #2
  • Create ClonableSavable( 7.1, 0 ) - ( creation || clone )
  • Cloning class: ClonableSavable, object: @26094370, clone: @20754125

** RESULTS **


NonClonableSavable loaded using noCloneKey:
no1 == no2: true ( @24347419 == @24347419 ) - REFERENCE!
ClonableSavable loaded using cloneKey:
no1 == no2: false ( @11650554 == @20754125 ) - CLONES!

@Momoko_Fan said: If you want to use the AssetManager with cloning you have to implement CloneableSmartAsset and load it with an AssetKey that is configured with the proper cache and processor ... This is the only means by which the AssetManager can keep track of the clones and properly remove the asset from the cache when no clones of it are present anymore and thus it isn't used. E.g. the AssetKey subclass should have this code:

[java]@Override
public Class<? extends AssetCache> getCacheType(){
return WeakRefCloneAssetCache.class;
}

@Override
public Class<? extends AssetProcessor> getProcessorType(){
    return CloneableAssetProcessor.class;
}[/java]

and the asset object itself should have key variable that can be modified from the methods setKey/getKey from CloneableSmartAsset interface.

But why is the caching for non-smart assets apparently inconsistent? User loading from multiple threads?