This post is all about Properties in Sgine for use primarily with Scala. However, I know many people, for whatever reason, are still using Java. For those people I would like to point you to this post I made a year previous regarding Properties in Java and the implementation of Properties in the open-source project xjava.
Basics
In Sgine Property is a trait that extends Function0[T] (getter) and Function1[T, Property[T]] (setter). This basic foundation allows better compliance of use along with functional delegation. Here's a simple example class implementation of a Property:
import org.sgine.property.Property import scala.reflect.Manifest class SimpleProperty[T](protected implicit val manifest: Manifest[T]) extends Property[T] { private var _value: T = _ def apply() = _value def apply(value: T): Property[T] = { _value = value this } }
This is relatively straight-forward to the way properties were discussed in the previous article with one glaring difference. The use of Manifest is a very cool feature in Scala that Sgine takes extensive use of. If you're not familiar with Manifest I suggest reading more here. If you are familiar with erasure Generics in Java you'll know that Generic type information is not available at runtime, it is purely a compile-time feature to maintain static-typing. However, there are many cases where having that information at runtime is extremely useful and that's exactly what scala.reflect.Manifest gives you. Fortunately though, the only case in which this knowledge will be necessary is if you need to extend Properties, which will never be necessary for the majority of developers.
Now that we have our SimpleProperty class we want to use it:
class Person { val name = new SimpleProperty[String]() } val person = new Person() person.name("John Doe") val name = person.name()
Now that we understand how easy it is to create properties, lets put that knowledge aside for now. As I stated, you'll probably never need to do this, but hopefully this helps you understand how easy it is to do if you should desire to do so.
Moving on, Sgine already supplies all the functionality demonstrated in SimpleProperty in org.sgine.property.MutableProperty. This class is the base for most of the standard mutable Property functionality in Sgine.
The above example requires the creation of your own Property class, but you can use the built-in MutableProperty:
import org.sgine.property.MutableProperty class Person { val name = new MutableProperty[String]() } val person = new Person() person.name("John Doe") val name = person.name()
Because setting name via apply:
person.name("John Doe")Is not the most intuitive to realize it is setting the value of the Property, there are a couple additional convenience methods in Property that invoke the same functionality but may be more readable in code.
The first is ":=" so you can write:
person.name := "John Doe"This is what I personally prefer when setting the value of a Property as it is quite clear that I am intentionally modifying the value of person.name.
Additionally you can use "value" for both getting and setting:
person.name.value = "John Doe" // Setter val name = person.name.value // Getter
Usage aside, Properties are quite simple in concept and though every implementation has its own details you must understand, you hopefully have a good foundation to start with. In the next sections we will be building upon this foundation with specific functionality that Sgine provides to make Properties that much more powerful and effective.
Events
Events in properties are arguably the most useful feature in any properties framework and definitely the most requested feature to any properties system. Sgine provides a complete Event system that is general purpose apart from properties, but is utilized in the properties system to provide a simple, yet complete event system.
The relevant trait we'll use for event handling is ListenableProperty. Here's a sample of setup:
import org.sgine.property.ListenableProperty import org.sgine.property.MutableProperty class Person { val name = new MutableProperty[String] with ListenableProperty[String] }
Notice we simply had to mix-in the ListenableProperty to our MutableProperty and now we've got event support in our "name" property.
Here's an example to listen for change of "name":
val person = new Person() person.name.listeners += nameChanged _
Notice here that we simply reference the "listeners" instance of "name" and invoke "+=" to add our listener.
The definition of our listener "nameChanged":
private def nameChanged(evt: PropertyChangeEvent[String]) = { println("Name Changed!") }
Like I stated previously, the event is extremely powerful and can do some really awesome things, but for the purposes of this tutorial we'll keep it to simply mentioning that we can reference as our "listener" any method/function that takes an Event and returns Unit. In the above code we are explicitly wanting to receive PropertyChangeEvents for Strings and the internal event system will filter all other events from getting to our listener method.
Now every time we change "name" an event will be thrown to "nameChanged".
Another tutorial needs to be written in the future to explain the features and functionality provided in the event system, but you now have the basic knowledge to listen for events in properties.
Delegate Properties
DelegateProperty is an alternative base property to MutableProperty. Instead of maintaining a value internally within the property itself, it delegates to outside functions for getting and setting values. This is most useful when dealing with existing code. Consider the following example:
import org.sgine.property.DelegateProperty class Window { private val frame = new javax.swing.JFrame() val title = new DelegateProperty[String](frame.getTitle _, frame.setTitle _) }This example is a simple wrapper around Swing's JFrame, and though we could create a title property, listen to changes, and then apply them to the underlying JFrame it would be quite a bit of effort (and a lot more code) to do so. Instead, since all we want to do is replicate the functionality from JFrame's getTitle/setTitle methods we can just delegate to and from those methods. This allows us to utilize all the features of properties while relying on existing functions.
This can also be useful for more complex scenarios where determining the value requires some internal processing. You simply specify your getter to point to a private method/function that does the work and you're finished. The setter function is an optional parameter and if unspecified will throw an UnsupportedOperationException if invoked.
Filtering
FilteredProperty provides the ability to modify an incoming value before it gets applied to the property itself. See the following example usage:
import org.sgine.property.FilteredProperty import org.sgine.property.MutableProperty class Person { val name = new MutableProperty[String] with FilteredProperty[String] { val filter = (s: String) => if (s == null) "" else s } }This simple example converts nulls to a blank String. This allows you to work with "name" without ever being worried about its value being null.
Another usage of this would be to disallow specific values by throwing an Exception:
import org.sgine.property.FilteredProperty import org.sgine.property.MutableProperty class Person { val name = new MutableProperty[String] with FilteredProperty[String] { val filter = (s: String) => if (s == null) throw new NullPointerException("This property does not allow null!") else s } }Now, this is creating boiler-plate code, and we don't like boiler-plate code. Lets do this in a more reusable way:
import org.sgine.property.FilteredProperty trait NullsAreBadFilteredProperty[T] extends FilteredProperty[T] { val filter = (value: T) => { if (value == null) { throw new NullPointerException("This property does not allow null!") } else { value } } }Now I can simply mix-in my new trait wherever I want to disallow nulls:
import org.sgine.property.MutableProperty class Person { val name = new MutableProperty[String] with NullsAreBadFilteredProperty[String] }
Transactional Properties
TransactionalProperty is the property equivalent of a database transaction. When a the property is created it maintains a reference to its initial value and the current value. At any time the value can be committed in order to roll forward the initial value to the current value or it may be reverted in order to roll back the current value to the initial value. Finally, when a TransactionalProperty is committed a PropertyTransactionEvent is thrown for the property that represents the original value and the new value.
import org.sgine.property.MutableProperty import org.sgine.property.TransactionalProperty class Person { val name = new MutableProperty[String]("") with TransactionalProperty[String] }
Simple usage:
val person = new Person() person.name := "John Doe" person.name.revert() // Reverts the value back to "" person.name := "Jane Doe" person.name.commit() // Commits "Jane Doe"
Animation
Animation may be a little confusing of a name here since we're dealing with properties and not images, but AnimatingProperty works with a PropertyAnimator to allow "animation" from the current value to the newly applied value over time. Obviously this is of primary benefit in the case of a user-interface, but can be applicable to other uses as well.
Here is a simple example to animate linearly from 0.0 to 5.0 over five seconds:
import org.sgine.property.AnimatingProperty import org.sgine.property.MutableProperty import org.sgine.property.animate.LinearNumericAnimator class Location { val x = new MutableProperty[Double](0.0) with AnimatingProperty[Double] x.animator = new LinearNumericAnimator(1.0) // Move 1.0 per second } val location = new Location() location.x := 5.0 // Begin animation from 0.0 to 5.0 location.x.waitForTarget() // Convenience method to wait for target value to be reachedWe're using LinearNumericAnimator, a built-in animator to work with Double properties, to animate 1.0 per second. If you are familiar with Flex or JavaFX this is similar in purpose to what they call "Effects". However, in those frameworks an Effect is defined apart from the property being modified and is simply activated upon it. Though this means writing explicit code for every movement you wish to make it is perfectly acceptable most of the time. However, this does create the potential problem of multiple effects being enacted on the same value at the same time. For example, a box is displayed in the center of the screen with two buttons. The first button moves the box to the left side of the screen and the second button moves the box to the right side of the screen. This is perfectly fine until you try hitting one right after another. If you don't add explicit support to cancel the first effect you will run into what I call "effect fighting". This is the scenario where two effects are trying to modify the same property to two different targets. The visual result of the box example is the box jumping to the left, then the right, then left, and so on with each effect attempting to update the position. Particularly in the case of Sgine, being a fully multi-threaded and thread-safe 3d engine this was of great concern. PropertyAnimator simplifies and abstracts the process, preventing effect fighting from occurring.
Consider another scenario. In the Location class we created above, if it were being managed in a layout manager of some sort, the layout manager wouldn't necessarily know anything about animation. By virtue of the fact that the layout manager modifies the "x" property, instead of immediately jumping to the new position, animation would occur.
LinearNumericAnimator is just one built-in PropertyAnimator. EasingNumericAnimator similarly works with Double properties, but uses easing functions instead of linear motion. Numbers are not the only things that can be animated though. The EasingColorAnimator works similarly to EasingNumericAnimator, but works on Colors. At the time of this writing those are the only built-in PropertyAnimators, but writing your own PropertyAnimator is a simple matter of providing your own implementation of the following method signature:
def apply(current: T, target: T, elapsed: Double): T
Easings with animation is generally more elegant than simple linear animation. Sgine has a full listing of easing functions supported:
- BackIn / BackOut / BackInOut
- BounceIn / BounceOut / BounceInOut
- CircularIn / CircularOut / CircularInOut
- CubicIn / CubicOut / CubicInOut
- ElasticIn / ElasticOut / ElasticInOut
- ExponentialIn / ExponentialOut / ExponentialInOut
- LinearIn / LinearOut / LinearInOut
- QuadraticIn / QuadraticOut / QuadraticInOut
- QuarticIn / QuarticOut / QuarticInOut
- QuinticIn / QuinticOut / QuinticInOut
- SineIn / SineOut / SineInOut
This concludes part 1 of this tutorial. Please continue on to part 2 for the conclusion.
Matt,
ReplyDeleteI see no reasons why beans should be deprecated.
1. I see no value with the possibility to attach listeners directly to the "property" -- typically in an event handler I have to know that property of __ceratin__ object is modified, so I need this object as event target, and hence, it's more logical to have "bean" rather that a property as event dispatcher that register listeners.
2. Example with filtering leads me to conclusion that you confuses terms "property" and "type". What you shows here is a custom user type with (kinda of) validation logic. It's possible to do any language, just don't use built-in types (like String) where custom type is necessary:
public class SSN {
public SSN(final String value) {
// validation here
}
}
...
public BeanThatShouldBeDeprecated {
private SSN ssn;
public SSN getSsn() { return ssn; }
public void setSsn(SSN value) { ssn = value; }
}
3. Transaction and Animation examples are only confirm my guess that you try to use "just invented" hammer at everything that looks more or less as a nail. It very doubtful architecture where "part" controls the "whole". And, trust me, Adobe Flex team has a lot of valid reasons to extract behavior of animation into Effect -- effects are all about coordinated change to the set of several(!) properties of multiple(!) objects. And God forbids controlling database transactions with your approach -- transaction is another common example of coordinated changes of several properties of multiple objects, not about reverting single value.
Even without databases, let us consider the following case: there is an object of type Rectangle that must be rotated/moved/resized upon user interactions. How would you implement it with your Transactional properties? I'm especially interested in "rotation" case -- the most complex one.
Valery
You don't know why people use Java? If you can't grasp the obvious then why would you be a credible source for anything?
ReplyDeleteval person = new Person()
ReplyDeleteperson.name := "John Doe"
person.name.revert() // Reverts the value back to ""
person.name := "Jane Doe"
person.name.commit() // Commits "Jane Doe"
Single property commit to, for example, a database will be hell on the performance.
It should be good to have the Property infrastructure in a commons library, then all the chain for user interfaces will come by itself.
ReplyDeleteDid I say I don't know why people use Java? That's odd, I don't remember saying that... :o
ReplyDeleteProperty commits are not intended to be a one-to-one relationship to say a database, but can be extremely useful for generating a SQL update with only the fields that were updated instead of doing a mass update for every field that might have changed.
@Camilo: I agree about having the Property infrastructure in a commons library. Currently there's a lot of functionality tied up in Sgine that will eventually be broken out into smaller general purpose APIs.