Cypher Poker Tutorial: Changing and Saving Start-Up Settings

In this article Patrik B., creator of Cypher poker, explains how to create and edit start-up configuration settings. The notes on the explanation were edited by myself. The tutorial is written from Patrick’s perspective.

Regarding auto-enabling certain functions at startup, that can be done (it’s included in the EthereumStatusWidget). Regarding storing data between sessions, that can also be done (it’s done through GlobalSettings for the lounge and PokerGameSettings when the game is active).

It can be a little tricky saving data to the settings. It’s a large XML object and every widget has a reference to its own settings within that object as well as to the whole tree.

Here’s how the EthereumStatusWidget auto-enables its connection at startup based on the settings:

try {
 var ethereumEnabled:Boolean = GlobalSettings.toBoolean(GlobalSettings.getSetting(“defaults”, “ethereum”).enabled); 
} catch (err:*) { 
 ethereumEnabled = false;
}
this.toggle.addEventListener(Event.CHANGE, this.onToggleClick); 
if (ethereumEnabled == true) {
 this.enableautolaunch.isSelected = true;
 this.enableautolaunch.invalidate();
}

The toggle event listener is just a part of the function (not something to focus on). Basically in the try..catch We’re trying to retrieve the “enabled” node within “ethereum” within “defaults”his is found in settings.xml:

<defaults>
 <rtmfpgroup>CypherPoker.Lounge</rtmfpgroup>
 <! — Leader status is assumed if no peer responses in leadertimeout seconds →
 <leadertimeout>1</leadertimeout> 
 <! — If concurrency is disabled, the maxcryptoworkers value is ignored (only one pseudo-worker in the main thread will be used) →
 <! — Additionally, if Worker functionality is unavailable in the runtime, this value will be forced to false. →
 <concurrency>true</concurrency>
 <! — Maximum number of concurrent crypto workers. Practical maximum is usually 3 but this should be measured per device. →
 <maxcryptoworkers>3</maxcryptoworkers>
 <! — Maximum period, in milliseconds, to retry an operation with a currently busy cryptoworker. →
 <workerbusyretry>999</workerbusyretry>
 <! — Maximum period, in milliseconds, to delay between some consecutive operations to accomodate multiple instances on the same device →
 <multiinstancedelay>150</multiinstancedelay>
 <! — Default Crypto Byte Length →
 <cryptobytelength>30</cryptobytelength>
 <ethereum>
 <enabled>false</enabled>

Notice the last node there … <enabled>false</enabled>

That’s what’s being accessed in the “try..catch”

The try part attempts to retrieve the data from the settings.xml but if it doesn’t exist (or something else goes wrong), the default setting is set in the catch part.

Then in if (ethereumEnabled == true) { I just start the connection (or not), based on the setting.

So you’ll probably want to add your own setting to some relevant section of the settings.xml file and then access it in the `initialize` function so that it does its thing at startup.

Most of the widgets work pretty much in the same way. Many of them don’t need startup info but some do.

The main setting is retrieved like this:

GlobalSettings.getSetting(“defaults”, “ethereum”).enabled

Which means, from the “GlobalSettings” (settings.xml data), find a node named “defaults”, within that find “ethereum”, and within that get “enabled”.

This is a little clunky because when I first wrote it I didn’t think I’d need to go more than 2 levels deep within the XML. The “.enabled” part is the third level, and it’s possible to go even deeper from there, but I’m trying to keep it fairly shallow.

If you set up your own node, let’s say it’s something like this:

<settings version=”2.1a”> 
 <netcliques>
 <definition id=”RTMFP_INET” type=”direct” class=”p2p3.netcliques.RTMFP”>
 <name>RTMFP on Internet</name>
 <description>Assisted peer-to-peer connectivity using Rendezvous and RTMFP over the internet.</description>
 <init>

 <defaults> 
 <ethtransferwidget>
 <enabled>true</enabled>

You would access it using: GlobalSettings.getSetting(“defaults”, “ethtransferwidget”).enabled

Calling `getSetting` returns a String object so you could store it as:

var startupenabled:String = GlobalSettings.getSetting(“defaults”, “ethtransferwidget”).enabled.toString();

Then compare the contents of the node like this:

if (startupenabled == “true”) {
 //do stuff
 } else {
 //do nothing
 }

In my version I use another function in GlobalSettings to convert it to a Boolean (true/false) value:

var ethereumEnabled:Boolean = GlobalSettings.toBoolean(GlobalSettings.getSetting(“defaults”, “ethereum”).enabled);

The toBooleanfunction is able to handle all sorts of variations like “true”, “false”, “enabled”, “disabled”, “1”, “0”, and so on. But if you can be fairly certain that you’ll know exactly what the data will be then you don’t need to do this.

Don’t forget to include import org.cg.GlobalSettings to use it!

Also, at the top of the Lounge class, enable reloading of config data from file:

public static const resetConfig:Boolean = true; //Load default global settings data at startup?

This data is saved to something called a Local Shared Object which is common to all of the runtimes that ActionScript supports. The same mechanism can be used to store config data for the desktop version, the web version, the mobile version, etc. The original (default) config data … settings.xml … remains on the disk.

Here’s a bit more on the SharedObject class if you’re interested: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/SharedObject.html

One thing you can try doing is setting some other string in the <enabled> node. Don’t convert it to a Boolean value, just read it as a string, and display it in the debug output to see if you’re actually accessing it. There’s a class for debugging to do this: org.cg.DebugView Import it and call it like this:

DebugView.addText(“Some text to add”);

You can add anything in there that you want to view, not just a string like I’m showing. The output will be displayed in FlashDevelop’s “output” window and you can also call up the internal debug view using ALT-D (or is it CTRL-D? one of the two :slightly_smiling_face: )

So you could do something like this:

DebugView.addText (“Widget enabled setting: “+GlobalSettings.getSetting(“defaults”, “ethtransferwidget”).enabled);

Since this will presumably be in the “initialize” function you should see this text appear in the output once the application has started. If there’s a problem, such as the XML being malformed or this node not being accessible, you’ll get a runtime error.

Saving the data is a little trickier. I usually break it down into parts to make it easier to read. Here’s where I store the Ethereum client address and port settings from the EthereumStatusWidget to the global settings:

var ethereumSettings:XML = GlobalSettings.getSetting(“defaults”, “ethereum”);
var addressNode:XML = ethereumSettings.child(“clientaddress”)[0];
var portNode:XML = ethereumSettings.child(“clientport”)[0];
addressNode.replace(“*”, new XML(“<![CDATA[“ + this.clientAddressInput.text + “]]>”));
portNode.replace(“*”, new XML(this.clientPortInput.text));
GlobalSettings.saveSettings();

The getSetting function returns an XML object. This won’t work:

var startupenabled:String = GlobalSettings.getSetting(“defaults”, “ethtransferwidget”).enabled;

var startupenabled:XML = GlobalSettings.getSetting(“defaults”, “ethtransferwidget”).enabled;

Or if we do want to use a string then:

var startupenabled:String = GlobalSettings.getSetting(“defaults”, “ethtransferwidget”).enabled.toString();

You’d get a compiler error if you tried this first line.

The following lines could probably use some explaining. When I get ethereumSettings in the first line, I then use that object to get the <address> node like this:

var addressNode:XML = ethereumSettings.child(“clientaddress”)[0];

The .child function returns an array of all child nodes within etherumSettings that match the name “clientaddress”. There should be only one so I’m simply using the first found instance like a standard array element: [0]

Once we have a reference to the node we want to work with, we replace all of its contents (children), by doing something like this:

addressNode.replace(“*”, new XML(“<![CDATA[“ + this.clientAddressInput.text + “]]>”));

This means .replace all (“*”) the contents of addressNode with a new XML object: XML(“<![CDATA[“ + this.clientAddressInput.text + “]]>”)

This looks a little convoluted since I’m using a CDATA node (https://en.wikipedia.org/wiki/CDATA)

It’s a little more obvious when I update the clientport node:

portNode.replace(“*”, new XML(this.clientPortInput.text))

The new XML object is being created with whatever text is found in the clientPortInput text field. This is something I added recently so you may not see it in your UI.

In the custom node we created for your widget, you would do something like this:

var startupenabled:XML = GlobalSettings.getSetting(“defaults”, “ethtransferwidget”).enabled;
startupenabled.replace(“*”, new XML(“nope”));

Here I’ve hard-coded “nope” as the string to set. The updated XML would look like this:

<defaults> 
 <ethtransferwidget>
 <enabled>nope</enabled>

It’s important to note that the entire XML tree is linked so that all we’ve really done here is to isolate which node we wanted to update, and updated it. We haven’t created a copy of the settings data or anything (many people get this confused). When I say that we get a “reference” to something, we’re simply pointing to an object and working with it, not creating a copy. It’s possible to actually create a copy but we’d have to explicitly do that.
 
In the final line, the updated XML data is saved to the local shared object:

GlobalSettings.saveSettings();

Probably the easiest part. When the application starts up next time, the shared object is read unless we directly specify that we want to load settings.xml in the Lounge class. If the shared object data doesn’t exist (which would happen if we’ve never run the application before), the settings.xml data is read automatically and stored to the shared object.

When we call saveSettings we’re literally saving the entire configuration object; all widgets, layouts, etc. If you wanted to you could fiddle with other widgets’ configurations and save them so that their data is changed when they startup next time. This is usually not a good idea unless the purpose of the widget is specifically to update other widgets … but there’s nothing to stop you from doing it!

You could re-arrange the order of widgets, change which components they have and how they appear, change which options are available for connectivity … literally anything that appears in the settings.xml data can be changed using the same procedures described above.

One final note, I’m working on the settings updater class which will take new settings data and add/update it in the global settings (in the shared object). I’m still finalizing the format for the “update.xml” file … not even sure if I’ll call it this … but one thing I’m sure of is that we’ll need to keep a track of nodes that need to be added or changed between versions. For example, if we want to add the EtherTransferWidget to the next version it will obviously need to be specified in the settings data so the updater will need to know what to add. So keep a track of anything you’ve added to, or updated in, the settings XML data.

Your widget could potentially take care of creating all of its own settings but it at least needs to be added to the <views> node somewhere so that the widget renderer can actually create it. If it’s not found in this node then it won’t be created, even if it’s been added to the compiled code.