Skip to content
langera edited this page Oct 8, 2014 · 2 revisions

Your data type

Start by defining the data type you want to store


public interface Bean {
        
    int getMyUnsignedInt();

    void setMyUnsignedInt(final int myUnsignedInt);

    long[] getMyLongArray();

    void setMyLongArray(final long[] myLongArray);            
}

Implement your POJO (so far - nothing new here except that your object implements an interface so the data type can also have a flyweight impl.)


public class BeanPojo implements Bean {

    private int myUnsignedInt;
    private long[] myLongArray;
    
    @Override
    public int getMyUnsignedInt() { return myUnsignedInt; }

    @Override
    public void setMyUnsignedInt(final int myUnsignedInt) { 
        if (myUnsignedInt < 0) {
            throw new IllegalArgumentException();
        } 
        this.myUnsignedInt = myUnsignedInt; 
    }

    @Override
    public long[] getMyLongArray() { return myLongArray; }

    @Override
    public void setMyLongArray(final long[] myLongArray) { this.myLongArray = myLongArray; }                     
}

Create a Slab for your objects

1. Define the storage to use by the slab. In this example we ask for a Storage using DIRECT memory (i.e off-heap) which can handle Long.MAX_VALUE for every chunk of slab (i.e Initial size).


    SlabStorageFactory storageFactory = 
        Storages.storageFactoryFor().maxCapacity(Long.MAX_VALUE).type(DIRECT).newInstance();

2. Define how and when to create a flyweight. In this example we will have one flyweight instance per Thread stored in ThreadLocal. For BeanFlyweightFactory - see below.


    FlyweightFactory flyweightFactory = 
        new ThreadLocalSlabFlyweightFactory<>(new BeanFlyweightFactory()));            

3. Factory to create specific BeanFlyweight instances for Beans with a long array of size = 3 see also factories like 'ThreadLocalSlabFlyweightFactory' or 'SingletonSlabFlyweightFactory' to control the number of flyweight instances created. BeanFlyweight is where the specific code to write and read from the storage is. (see below).


public class BeanFlyweightFactory implements SlabFlyweightFactory<Bean> {

    @Override
    public SlabFlyweight<Bean> getInstance() { return new BeanFlyweight(3); }
}    

4. Define the Address Strategy In this example we use the trivial DirectAddressStrategy which does not translate the addresses. This does mean that we probably don't store the address as a reference to a specific object state. If we are, we'll have to keep changing it when it is moved to a different address (could happen due to compaction). The strategy contains the hooks for translating between virtual and real slab addresses and the hooks when a real address moves.


    AddressStrategy addressStrategy = new DirectAddressStrategy()

5. Finally, create the slab.


    Slab<Bean> slab = 
        new Slab<>(storageFactory, INITIAL_SIZE_IN_BYTES, addressStrategy, flyweightFactory);

Example usage of the slab


    long address = slab.add(myBean)
    
    Bean thisIsTheFlyweightInstance = slab.get(address);
    
    slab.remove(address);
    
    for (Bean alsoTheFlyweight : slab) {
        System.out.println(alsoTheFlyweight.getMyUnsignedInt());
    }

The Slab Flyweight object

The real stuff.... To be used by the slab to write the data, returned to access the data and manage free entries


public class BeanFlyweight  extends AbstractSlabFlyweight<Bean> implements Bean {

    private final int myLongArrayFixedSize;   
    private int myUnsignedIntOffset;
    private int myLongArrayOffset;

    public BeanFlyweight(final int myLongArrayFixedSize) {
        this.myLongArrayFixedSize = myLongArrayFixedSize;
    }

    /**
     * map this flyweight instance to a specific storage and address.
    **/
    @Override
    public void map(final SlabStorage storage, final long address) {
        super.map(storage, address);
        this.myUnsignedIntOffset = 0;
        this.myLongArrayOffset= this.myUnsignedIntOffset + storage.getIntOffset();
         // we use the longArray memory when the slab is empty to manage the free address
        setFreeAddressOffset(this.myLongArrayOffset);
    }
    
    /**
     * return true iff the position represented by storage and address is empty.
     * 
     * This example shows why managing the state of an empty slab position is done here. 
     * It allows us a much more optimised use of the memory by piggy backing on the same 
     * memory used by a valid "Bean".
     * Here, we count on the fact that our "myUnsignedInt" property is unsigned and so a 
     * negative value can work as a null flag. 
    **/
    @Override
    public boolean isNull(final SlabStorage storage, final long address) {
        // piggy back on unsigned int to reduce memory for null flag
        return storage.getInt(address + myUnsignedIntOffset) < 0;  
    }              
    
    /**
     * sets the state represented by the storage and address to be null 
     * (i.e empty position in the slab)
    **/        
    @Override
    public void setAsFreeAddress(final SlabStorage storage, 
                                 final long address, 
                                 final long nextFreeAddress) {
        super.setAsFreeAddress(storage, address, nextFreeAddress);
        // piggy back on unsigned int to reduce memory for null flag
        storage.setInt(-1, address + myUnsignedIntOffset); 
    }                 

    /**
     * sets the state represented by the storage and address to that of 'bean'.
    **/    
    @Override
    public void dumpToStorage(final Bean bean, final SlabStorage storage, final long address) {
        long offset = storage.setInt(bean.getMyUnsignedInt(), address);
        storage.setLongArray(bean.getMyLongArray(), offset);
    }

    /**
     * The slab has to know the memory size needed for the state of a 'Bean'. 
     * This method calculates and returns it (per storage). 
    **/    
    @Override
    public int getStoredObjectSize(final SlabStorage storage) {
        return storage.getIntOffset() + storage.getLongArrayOffset(myLongArrayFixedSize);
    }

    /**
     * convenience method for the code to view the flyweight as a Bean. 
    **/
    @Override
    public Bean asBean() {
        return this;
    }

    //////////////////////////////////////////////////////////
    // Bean implementation uses backend storage for its state.
    //////////////////////////////////////////////////////////
    
    @Override
    public int getMyUnsignedInt() {
        return getStorage().getInt(getMappedAddress() + myUnsignedIntOffset);
    }

    @Override
    public void setMyUnsignedInt(final int myUnsignedInt) {
        getStorage().setInt(myUnsignedInt, getMappedAddress() + myUnsignedIntOffset);
    }

    @Override
    public long[] getMyLongArray() {
        long[] container = new long[myLongArrayFixedSize];
        return getStorage().getLongArray(container, getMappedAddress() + myLongArrayOffset);
    }

    @Override
    public void setMyLongArray(final long[] myLongArray) {
        getStorage().setLongArray(myLongArray, getMappedAddress() + myLongArrayOffset);
    }     
}