I am seeking advice on the efficiency and long term implications of various ways of capturing modifications to instance variables that I have tried implementing. Essentially, this is setup:
In my framework, I originally had a class whose sub classes were only supposed to contain public instance variables - kind of like a struct
in C. These classes were pretty much just for convenience in wrapping up multiple objects and primitives into one object.
Now, however, I need a way to individually listen for modifications to each of the instance variables. For example, when someone modifies the rotation field of a Transform
object (featuring position, rotation, etc.), ideally a list of listeners would all be fired with some event that only pertains to the rotation variable - I don't care about the variables that weren't modified. Of course, you could add and remove listeners from this list.
My initial "solution" was an absolute hack. I essentially spin polled the variables I was interested in, stored the previous value, and examined if the new value matched the old one.
My first thought at remedying this was to change my setup from public instance variables to private variables with getters and setters for access (which I didn't do earlier for simplicity and code compactness). I would also maintain a list of listeners in every struct and notify each one when a setter was called.
However, this approach fell flat for two reasons:
First of all, what if a 3rd party extending this class forgot (or simply didn't know) to notify the listeners? How would I guarantee that they would implement the class in a way that wouldn't break other parts of the framework depending on these events? Another problem was that of mutable objects - even if clients correctly implemented getters and setters by notifying the listeners when a setter was called, there was no way of detecting when an object was retrieved with a getter and tinkered with internally.
My current approach seems from my view to be the most elegant, but runs into possible performance issues when autoboxing primitives. Here's the structure:
A DataComponent
is the class that originally was intended to behave like a struct - it now contains a map of String keys to generic DataField
s, which represent one instance variable.
DataComponent:
/**
* A Component that holds a Map of keys to DataFields. Subclasses should not have any instance variables; rather, they
* should add DataFields and allow the data to be accessed in this way.
*/
public abstract class DataComponent extends Component {
private Map<String, DataField> m_dataFields;
{
m_dataFields = new HashMap<String, DataField>();
}
public DataComponent() {
super();
}
/**
* Puts a field for a specific key.
*
* @param key
* @param field
*/
protected void putField(String key, DataField field) {
m_dataFields.put(key, field);
}
/**
* Removes a key from the Map.
*
* @param key
*/
public void removeField(String key) {
m_dataFields.remove(key);
}
/**
* Returns the DataField associated with the key.
*
* @param key
* @return
*/
public DataField getField(String key) {
return m_dataFields.get(key);
}
}
DataField:
/**
* A DataField for one T Object.
*
* @param <T>
*/
public class DataField<T extends Object> extends MutationNotifier {
private T m_data;
private FieldMutationListener m_listener;
{
m_listener = new FieldMutationListener();
}
/**
* Constructs an empty DataField.
*/
public DataField() {
}
/**
* Constructs a DataField with the specified initial data.
*
* @param data
*/
public DataField(T data) {
m_data = data;
}
/**
* Sets the Data in this DataField. If the data extends MutationNotifyer, a MutationListener is added to the Object.
*
* @param data
*/
protected void set(T data) {
// If the old data extends MutationNotifier, meaning it's mutable, remove the listener that was previously
// added.
if (MutationNotifier.class.isAssignableFrom(m_data.getClass())) {
((MutationNotifier) m_data).removeMutationListener(m_listener);
}
// If the new data extends MutationNotifier, meaning it's mutable, add a listener so that we can notify our
// listeners when the object is changed.
if (MutationNotifier.class.isAssignableFrom(data.getClass())) {
((MutationNotifier) data).addMutationListener(m_listener);
}
m_data = data;
// Since the old object was set to a new one, notify the listeners that the field has changed.
this.notifyListeners();
}
/**
* Gets the data in this field.
*
* @return
*/
protected T get() {
return m_data;
}
private class FieldMutationListener implements MutationListener {
@Override
public void mutationOccurred() {
DataField.this.notifyListeners();
}
}
}
MutationNotifier:
/**
* A class that should be extended by a mutable object being added to a DataField. If the DataField sees that the Object
* extends MutationNotifier, it will add a listener so it is notified of any changes.
*/
public class MutationNotifier {
private List<MutationListener> m_listeners;
{
m_listeners = new ArrayList<MutationListener>();
}
/**
* Constructs a MutationNotifier.
*/
public MutationNotifier() {
}
/**
* Adds a listener.
*
* @param listener
*/
public void addMutationListener(MutationListener listener) {
m_listeners.add(listener);
}
/**
* Removes a listener.
*
* @param listener
*/
public void removeMutationListener(MutationListener listener) {
m_listeners.remove(listener);
}
/**
* Notifies all listeners that a mutation occured.
*/
public void notifyListeners() {
for (MutationListener listener : m_listeners)
listener.mutationOccurred();
}
}
My concern with this is that former primitive instance variables now must be converted to full-blown objects. Given this framework is going to go into a game engine, this may be an issue (a possible way around this would be to offer a PrimitiveDataComponent
that does the entire public instance variable thing without the embellishments - if you care about performance and don't need to listen for changes).
However, does anyone think autoboxing will reduce performance enough to make this necessary? Is there another, more efficient way of getting the effect I want? Any general programming tips would also be appreciated.