Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Tons of changes. Added support for both time and frame based synchron…

…ization. Added support for custom string-based message passing (Passing entire flash events seems unrealistic due to object reference issues). Expanded example to support both time and frame sync modes, and to demonstrate custom message passing. Added "quit all" method. Moved events to package. Added "start" and "stop" events. Added getTime() method for use with time-based sync.
  • Loading branch information...
commit d293e0f5cf46dad722053a1951978f8b9cb686ac 1 parent 94da48a
ericmika authored
View
16 FlashSpanExample/src/Ball.as
@@ -0,0 +1,16 @@
+package {
+ import flash.display.Sprite;
+
+ public class Ball extends Sprite {
+
+ public var vx:Number;
+ public var vy:Number;
+
+ public function Ball(color:uint) {
+ super();
+ this.graphics.beginFill(color);
+ this.graphics.drawEllipse(0, 0, 20, 20);
+ this.graphics.endFill();
+ }
+ }
+}
View
227 FlashSpanExample/src/FlashSpanExample.as
@@ -2,26 +2,44 @@ package {
import com.bit101.components.*;
import com.demonsters.debugger.MonsterDebugger;
import com.kitschpatrol.flashspan.FlashSpan;
- import com.kitschpatrol.flashspan.SyncEvent;
+ import com.kitschpatrol.flashspan.Settings;
+ import com.kitschpatrol.flashspan.SpanSprite;
+ import com.kitschpatrol.flashspan.events.CustomMessageEvent;
+ import com.kitschpatrol.flashspan.events.FlashSpanEvent;
+ import com.kitschpatrol.flashspan.events.FrameSyncEvent;
+ import com.kitschpatrol.flashspan.events.TimeSyncEvent;
import flash.desktop.NativeApplication;
import flash.display.Sprite;
+ import flash.display.StageAlign;
+ import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.InvokeEvent;
+ import flash.events.MouseEvent;
+ import flash.utils.getTimer;
-
public class FlashSpanExample extends Sprite {
private var flashSpan:FlashSpan;
+ private var spanSprite:SpanSprite;
+
+
+ private var titleLabel:Label;
+ private var syncLabel:Label;
+ private var eventLabel:Label;
+ private var fpsMeter:FPSMeter;
+
+ private const ballCount:int = 10;
+ private var balls:Array = [];
+
+ private var lastTime:int = 0;
public function FlashSpanExample() {
// catch command line args
NativeApplication.nativeApplication.addEventListener(InvokeEvent.INVOKE, onInvoke);
}
-
- private var label:Label;
-
+
private function onInvoke(e:InvokeEvent):void {
MonsterDebugger.initialize(this);
MonsterDebugger.trace(this, "Command line args: " + e.arguments);
@@ -38,16 +56,59 @@ package {
flashSpan = new FlashSpan();
}
- new PushButton(this, 5, 5, "Start", onStartButton);
- new PushButton(this, 5, 25, "Stop", onStopButton);
+ // set up the stage, make sure it's the correct size (some padding seems to get thrown in by the OS)
+ stage.scaleMode = StageScaleMode.NO_SCALE;
+ stage.align = StageAlign.TOP;
+ stage.nativeWindow.width = flashSpan.settings.thisScreen.screenWidth;
+ stage.nativeWindow.height = flashSpan.settings.thisScreen.screenHeight + 20; // compensate for title bar
+
+ // the span sprite, includes automatic translation compensation
+ spanSprite = flashSpan.getSpanSprite();
+ addChild(spanSprite);
+
+ // set up the GUI
+ fpsMeter = new FPSMeter();
+ fpsMeter.start();
+
+ titleLabel = new Label(this, 5, 5, "FlashSpan Example\nClick anywhere to add a ball. Using " + flashSpan.settings.syncMode + " sync mode.");
+ titleLabel.textField.textColor = 0x000000;
+ syncLabel = new Label(this, 5, 35, "Waiting To start.\tFPS: " + fpsMeter.fps);
+ eventLabel = new Label(this, 5, 50, "");
+
+
+
+ new PushButton(this, 5, 70, "Start", onStartButton);
+ new PushButton(this, 5, 95, "Stop", onStopButton);
+ new PushButton(this, 5, 120, "Add Balls", onAddBallsButton);
+ new PushButton(this, 5, 145, "Clear Balls", onClearBallsButton);
+ new PushButton(this, 5, 170, "Quit All", onQuitButton);
+
+ // listen for events from FlashSpan
+ flashSpan.addEventListener(FlashSpanEvent.START, onStart);
+ flashSpan.addEventListener(FlashSpanEvent.STOP, onStop);
+ flashSpan.addEventListener(CustomMessageEvent.MESSAGE_RECEIVED, onCustomMessageReceived);
+
+ // only listen for frame sync if we're using that instead of time mode
+ if (flashSpan.settings.syncMode == Settings.SYNC_FRAMES) {
+ flashSpan.addEventListener(FrameSyncEvent.SYNC, onFrameSync);
+ }
- label = new Label(this, 20, 20, "no");
- label.scaleX = 5;
- label.scaleY = 5;
+ // listen for local events, these will get passed on through a FlashSpan custom message
+ spanSprite.addEventListener(MouseEvent.CLICK, onMouseClick);
- flashSpan.addEventListener(FlashSpan.SYNC_EVENT, onFrameSync);
+ // add some initial balls
+ addBalls();
+ }
+
+
+ // For Time based updates
+ private function onEnterFrame(e:Event):void {
+ syncLabel.text = "Local Time: " + getTimer() + "\tServer Time: " + flashSpan.getTime() + "\tFPS: " + fpsMeter.fps;
+ updateBallsByTime();
}
+
+ // UI Event Callbacks
private function onStartButton(e:Event):void {
flashSpan.start();
}
@@ -56,9 +117,149 @@ package {
flashSpan.stop();
}
- private function onFrameSync(e:SyncEvent):void {
- label.text = e.frameCount.toString();
+ private function onAddBallsButton(e:Event):void {
+ flashSpan.broadcastCustomMessage("a");
+ }
+
+ private function onClearBallsButton(e:Event):void {
+ flashSpan.broadcastCustomMessage("c");
+ }
+
+ private function onQuitButton(e:Event):void {
+ flashSpan.quitAll();
+ }
+
+ private function onMouseClick(e:MouseEvent):void {
+ // send a CSV of mouse X and Y coordinates to everyone
+ flashSpan.broadcastCustomMessage("m", e.localX + "," + e.localY);
+ }
+
+
+ // FlashSpan Event Callbacks
+ private function onCustomMessageReceived(e:CustomMessageEvent):void {
+ eventLabel.text = "Custom message event fired: ";
+
+ if (e.header == "m") {
+ // it's a mouse click
+ eventLabel.text += "Mouse Click";
+
+ var mousePosition:Array = e.message.split(",");
+ var mx:int = mousePosition[0];
+ var my:int = mousePosition[1];
+
+ // Add a new ball at the mouse
+ var ball:Ball = new Ball(0x000000);
+ ball.x = mx - (ball.width / 2);
+ ball.y = my - (ball.height / 2);
+ ball.vx = (flashSpan.random() > 0.5) ? -flashSpan.random() * 15 : flashSpan.random() * 15;
+ ball.vy = (flashSpan.random() > 0.5) ? -flashSpan.random() * 15 : flashSpan.random() * 15;
+ spanSprite.addChild(ball);
+ balls.push(ball);
+ }
+ else if (e.header == "a") {
+ eventLabel.text += "Add Balls";
+ // it's a ball request
+ addBalls();
+ }
+ else if (e.header == "c") {
+ eventLabel.text += "Clear Balls";
+
+ // remove all the balls
+ while (balls.length > 0) {
+ spanSprite.removeChild(balls.pop());
+ }
+ }
+ }
+
+ private function onStart(e:Event):void {
+ eventLabel.text = "Start event fired";
+
+ // If we're using time, then use local frame callback
+ if (flashSpan.settings.syncMode == Settings.SYNC_TIME) {
+ addEventListener(Event.ENTER_FRAME, onEnterFrame);
+ }
+ }
+
+ private function onStop(e:Event):void {
+ eventLabel.text = "Stop event fired";
+
+ if (flashSpan.settings.syncMode == Settings.SYNC_TIME) {
+ removeEventListener(Event.ENTER_FRAME, onEnterFrame);
+ }
+ }
+
+ private function onFrameSync(e:FrameSyncEvent):void {
+ syncLabel.text = "Frame number: " + e.frameCount + "\tFPS: " + fpsMeter.fps;
+ updateBallsByFrame();
+ }
+
+
+ // Ball Simulation Stuff
+ private function updateBallsByFrame():void {
+ // update the balls by one frame
+ for (var i:int = 0; i < balls.length; i++) {
+ var ball:Ball = balls[i];
+
+ ball.x += ball.vx;
+ ball.y += ball.vy;
+
+ // bounce off edges
+ handleCollisions(ball);
+ }
+ }
+
+ private function updateBallsByTime():void {
+ if (lastTime > 0) {
+ var elapsed:int = flashSpan.getTime() - lastTime;
+
+ // update the balls by one frame
+ for (var i:int = 0; i < balls.length; i++) {
+ var ball:Ball = balls[i];
+
+ ball.x += (ball.vx * (elapsed / 100));
+ ball.y += (ball.vy * (elapsed / 100));
+
+ // bounce off edges
+ handleCollisions(ball);
+ }
+ }
+
+ lastTime = flashSpan.getTime();
+ }
+
+ private function handleCollisions(ball:Ball):void {
+ // left and right
+ if ((ball.x + ball.width) > spanSprite.totalWidth) {
+ ball.x = spanSprite.totalWidth - ball.width;
+ ball.vx *= -1;
+ }
+ else if (ball.x < 0) {
+ ball.x = 0;
+ ball.vx *= -1;
+ }
+
+ // top and bottom
+ if ((ball.y + ball.height) > spanSprite.totalHeight) {
+ ball.y = spanSprite.totalHeight - ball.height;
+ ball.vy *= -1;
+ }
+ else if (ball.y < 0) {
+ ball.y = 0;
+ ball.vy *= -1;
+ }
}
+
+ private function addBalls():void {
+ for (var i:int = 0; i < ballCount; i++) {
+ var ball:Ball = new Ball(flashSpan.random() * uint.MAX_VALUE);
+ ball.x = flashSpan.random() * (spanSprite.totalWidth - ball.width);
+ ball.y = flashSpan.random() * (spanSprite.totalHeight - ball.height);
+ ball.vx = (flashSpan.random() > 0.5) ? -flashSpan.random() * 15 : flashSpan.random() * 15;
+ ball.vy = (flashSpan.random() > 0.5) ? -flashSpan.random() * 15 : flashSpan.random() * 15;
+ spanSprite.addChild(ball);
+ balls.push(ball);
+ }
+ }
}
}
View
7 FlashSpanLibrary/.flexLibProperties
@@ -5,7 +5,12 @@
<classEntry path="com.kitschpatrol.flashspan.Settings"/>
<classEntry path="com.kitschpatrol.flashspan.NetworkedScreen"/>
<classEntry path="com.kitschpatrol.flashspan.CertifiedPacket"/>
- <classEntry path="com.kitschpatrol.flashspan.SyncEvent"/>
+ <classEntry path="com.kitschpatrol.flashspan.events.FrameSyncEvent"/>
+ <classEntry path="com.kitschpatrol.flashspan.SpanSprite"/>
+ <classEntry path="com.kitschpatrol.flashspan.Random"/>
+ <classEntry path="com.kitschpatrol.flashspan.events.TimeSyncEvent"/>
+ <classEntry path="com.kitschpatrol.flashspan.events.CustomMessageEvent"/>
+ <classEntry path="com.kitschpatrol.flashspan.events.FlashSpanEvent"/>
</includeClasses>
<includeResources/>
<namespaceManifests/>
View
267 FlashSpanLibrary/src/com/kitschpatrol/flashspan/FlashSpan.as
@@ -1,11 +1,15 @@
package com.kitschpatrol.flashspan
{
import com.demonsters.debugger.MonsterDebugger;
+ import com.kitschpatrol.flashspan.events.CustomMessageEvent;
+ import com.kitschpatrol.flashspan.events.FlashSpanEvent;
+ import com.kitschpatrol.flashspan.events.FrameSyncEvent;
+ import com.kitschpatrol.flashspan.events.TimeSyncEvent;
- import flash.display.Sprite;
+ import flash.desktop.NativeApplication;
import flash.events.DatagramSocketDataEvent;
+ import flash.events.Event;
import flash.events.EventDispatcher;
- import flash.events.IEventDispatcher;
import flash.events.TimerEvent;
import flash.net.DatagramSocket;
import flash.net.NetworkInfo;
@@ -27,8 +31,7 @@ package com.kitschpatrol.flashspan
// certified response format
// (certified response header)(certified packets sent count)
- // Events, where to put
- public static const SYNC_EVENT:String = "syncEvent";
+
// Todo move into connection class?
private var udpSocket:DatagramSocket = new DatagramSocket();
@@ -40,19 +43,27 @@ package com.kitschpatrol.flashspan
private var isServer:Boolean = false;
private var isSyncing:Boolean = false;
private var syncTimer:Timer;
+ private var serverTime:int = 0;
+ private var serverTimeReceived:int = 0;
// Message types
- public static const CERTIFIED_HEADER:String = 'c';
- public static const CERTIFIED_RESPONSE_HEADER:String = 'r';
- public static const PING_HEADER:String = 'p';
-
- public static const START_HEADER:String = 's';
- public static const STOP_HEADER:String = 't';
- public static const SYNC_HEADER:String = 'y';
+ internal static const CERTIFIED_HEADER:String = "c";
+ private static const CERTIFIED_RESPONSE_HEADER:String = "r";
+ private static const PING_HEADER:String = "p";
+
+ private static const START_SYNC_HEADER:String = "s";
+ private static const START_TIME_SYNC_HEADER:String = "n";
+ private static const STOP_REQUEST_HEADER:String = "t";
+ private static const STOP_COMPLETE_HEADER:String = "o";
+ private static const FRAME_SYNC_HEADER:String = "y";
+ private static const TIME_SYNC_HEADER:String = "m";
+ private static const QUIT_HEADER:String = "q";
+ private static const CUSTOM_MESSAGE_HEADER:String = "e";
public var frameCount:uint = 0;
+ private var seededRandom:Random;
- public function FlashSpan(screenID:int = -1, settingsPath:String = "settings.xml") {
+ public function FlashSpan(screenID:int = -1, settingsPath:String = "flash_span_settings.xml") {
super(null);
// set up debugger
@@ -63,6 +74,8 @@ package com.kitschpatrol.flashspan
settings = new Settings();
settings.load(settingsPath);
+ // seed it... TODO pass in seed from settings?
+ seededRandom = new Random(1);
// if screen ID is -1, use the IP identification technique
if (screenID == -1) {
@@ -79,7 +92,7 @@ package com.kitschpatrol.flashspan
// Close the socket if it's already open
if (udpSocket.bound) {
- MonsterDebugger.trace(this, 'Closing existing port');
+ MonsterDebugger.trace(this, "Closing existing port");
udpSocket.close();
udpSocket = new DatagramSocket();
}
@@ -96,18 +109,20 @@ package com.kitschpatrol.flashspan
// Start checking for who is connected
// not needed?
-// settings.thisScreen.connected = true; // obviosly we're connected
+// settings.thisScreen.connected = true; // obviously we're connected
// connectionCheckTimer = new Timer(500); // check every 500ms?
// connectionCheckTimer.addEventListener(TimerEvent.TIMER, connectionCheck);
// connectionCheckTimer.start();
-
- // based on frame rate? 60fps?
- syncTimer = new Timer(10);
- syncTimer.addEventListener(TimerEvent.TIMER, onSyncTimer);
- syncTimer.stop();
}
+ // For Time Sync Mode
+ public function getTime():int {
+ // add local time elapsed since last server time update to server time
+ return serverTime + (getTimer() - serverTimeReceived);
+ }
+
+
// heartbeat
private function connectionCheck(e:TimerEvent):void {
MonsterDebugger.trace(this, "Pinging for connection");
@@ -116,45 +131,163 @@ package com.kitschpatrol.flashspan
}
- private function onSyncTimer(e:TimerEvent):void {
+ private function onFrameSyncTimer(e:TimerEvent):void {
// broadcast sync
- broadcastMessage(SYNC_HEADER);
+ broadcastMessage(FRAME_SYNC_HEADER);
+ dispatchFrameSyncEvent();
+ }
+
+
+ private var millis:int;
+ private function onTimeSyncTimer(e:TimerEvent):void {
+ // broadcast sync
+ millis = getTimer();
- // send out event locally
- this.dispatchEvent(new SyncEvent(SYNC_EVENT));
+ // broadcast, factoring latency for each client
+ for each (var screen:NetworkedScreen in settings.networkMap) {
+ if (screen != settings.thisScreen) {
+ sendCertified(screen, TIME_SYNC_HEADER + (millis - (screen.latency / 2)));
+ }
+ }
+
+ // dispatch event locally for server
+ dispatchTimeSyncEvent(millis);
}
+
public function start():void {
- if (isServer) {
- frameCount = 0;
- syncTimer.reset();
- syncTimer.start();
+ if (isServer) {
+ // start syncing
+ if (settings.syncMode == Settings.SYNC_FRAMES) {
+ startFrameSync();
+ }
+ else if (settings.syncMode == Settings.SYNC_TIME) {
+ startTimeSync();
+ }
+ else {
+ throw new Error("Invalid screen sync mode. Check your settings.xml file.");
+ }
}
else {
- // send to server
- sendCertified(settings.networkMap[0], START_HEADER);
+ // clients should send request to server
+ sendCertified(settings.networkMap[0], START_SYNC_HEADER);
}
}
+
+
+ private function startFrameSync():void {
+ syncTimer = new Timer(1000 / 50); // TODO best approach to this?
+ syncTimer.addEventListener(TimerEvent.TIMER, onFrameSyncTimer);
+ syncTimer.start();
+ }
+
+
+ private function startTimeSync():void {
+ syncTimer = new Timer(250); // figure this out
+ syncTimer.addEventListener(TimerEvent.TIMER, onTimeSyncTimer);
+ syncTimer.start();
+ }
+
+
+ public function quitAll():void {
+ // broadcast
+ broadcastMessage(QUIT_HEADER);
+
+ // quit self
+ quit();
+ }
+
+
+ private function quit():void {
+ NativeApplication.nativeApplication.exit();
+ }
+
public function stop():void {
- if (isServer) {
- // Stop broadcasting sync messages
- syncTimer.reset();
- syncTimer.stop();
+ if (isSyncing) {
+ if (isServer) {
+ // Stop broadcasting sync messages
+ syncTimer.reset();
+ syncTimer.stop();
+
+ // remove event listeners
+ if (settings.syncMode == Settings.SYNC_FRAMES) {
+ syncTimer.removeEventListener(TimerEvent.TIMER, onFrameSyncTimer);
+ }
+ else if (settings.syncMode == Settings.SYNC_TIME) {
+ syncTimer.removeEventListener(TimerEvent.TIMER, onTimeSyncTimer);
+ }
+
+
+ // broadcast stop to clients
+ broadcastMessage(STOP_COMPLETE_HEADER);
+
+ // dispatch stop event locally
+ dispatchStopEvent();
+ }
+ else {
+ // send to server
+ sendCertified(settings.networkMap[0], STOP_REQUEST_HEADER);
+ }
}
- else {
- // send to server
- sendCertified(settings.networkMap[0], STOP_HEADER);
- }
- }
+ }
+
+ // Event dispatch wrappers
+ private function dispatchFrameSyncEvent():void {
+ // send out event locally
+ if (!isSyncing) {
+ dispatchStartEvent();
+ }
+
+ this.dispatchEvent(new FrameSyncEvent(FrameSyncEvent.SYNC, frameCount));
+ frameCount++;
+ }
+
+ private function dispatchTimeSyncEvent(time:int):void {
+ // send out event locally
+ if (!isSyncing) {
+ dispatchStartEvent();
+ }
+
+ // Record the time
+ serverTime = time;
+ serverTimeReceived = getTimer();
+
+ // Why even listen to this? Client should use .getTime() instead.
+ this.dispatchEvent(new TimeSyncEvent(TimeSyncEvent.SYNC, time));
+ }
+
+
+ private function dispatchStopEvent():void {
+ isSyncing = false;
+ this.dispatchEvent(new Event(FlashSpanEvent.STOP));
+ }
+
+
+ private function dispatchStartEvent():void {
+ isSyncing = true;
+ this.dispatchEvent(new Event(FlashSpanEvent.START));
+ }
+
+
+ // returns a full-screen span sprite at the correct offset
+ public function getSpanSprite():SpanSprite {
+ return new SpanSprite(settings.thisScreen.screenWidth, settings.thisScreen.screenHeight, settings.totalWidth, settings.totalHeight, settings.thisScreen.xOffset, settings.thisScreen.yOffset);
+ }
+ public function random():Number {
+ // nicely seeded random
+ return seededRandom.random();
+ }
+
+
private function onDataReceived(e:DatagramSocketDataEvent):void {
var incoming:String = e.data.readUTFBytes(e.data.bytesAvailable);
- MonsterDebugger.trace(this, "Received from " + e.srcAddress + ":" + e.srcPort + "> " + incoming);
+ //MonsterDebugger.trace(this, "Received from " + e.srcAddress + ":" + e.srcPort + "> " + incoming);
if (incoming.length > 0) {
var header:String = incoming.substr(0, 1); // first character
@@ -187,7 +320,7 @@ package com.kitschpatrol.flashspan
case CERTIFIED_RESPONSE_HEADER:
// Calculate packet time, disarm timer, etc.
var index:int = findPacketInWaitingIndex(parseInt(body));
- MonsterDebugger.trace(this, "Latency: " + (getTimer() - packetsInWaiting[index].timeSent) + "ms");
+ packetsInWaiting[index].destination.latency = getTimer() - packetsInWaiting[index].timeSent;
// disable the alarm!
packetsInWaiting[index].disarmTimeout();
@@ -195,25 +328,43 @@ package com.kitschpatrol.flashspan
// mark respondent as connected
packetsInWaiting[index].destination.connected = true;
-
// remove the packet in waiting
packetsInWaiting.splice(index, 1);
break;
// for server
- case START_HEADER:
- start();
- break;
+ case START_SYNC_HEADER:
+ start();
- case STOP_HEADER:
+ case STOP_REQUEST_HEADER:
stop();
break;
+ case STOP_COMPLETE_HEADER:
+ // Dispatch stop event
+ dispatchStopEvent();
+ break;
+
+ case QUIT_HEADER:
+ quit();
+ break;
+
// for broadcast
- case SYNC_HEADER:
- this.dispatchEvent(new SyncEvent(SYNC_EVENT));
- // dispatch event
- // TODO
+ case FRAME_SYNC_HEADER:
+ dispatchFrameSyncEvent()
+ break;
+
+ case TIME_SYNC_HEADER:
+ // Extract time from body!
+ dispatchTimeSyncEvent(parseInt(body));
+ break;
+
+ case CUSTOM_MESSAGE_HEADER:
+ var commaIndex:int = body.indexOf(",");
+ var customHeader:String = body.substring(0, commaIndex);
+ var customBody:String = body.substring(commaIndex + 1);
+
+ dispatchCustomMessageEvent(customHeader, customBody);
break;
default:
@@ -223,7 +374,6 @@ package com.kitschpatrol.flashspan
}
}
-
protected function onTimeout(packet:CertifiedPacket):void {
MonsterDebugger.trace(this, "Send timed out!");
@@ -239,7 +389,6 @@ package com.kitschpatrol.flashspan
}
-
// Sends a packet
private function send(screen:NetworkedScreen, message:String):void {
//Create a message in a ByteArray
@@ -268,7 +417,7 @@ package com.kitschpatrol.flashspan
// sends a message to everyone except for the sender
- public function broadcastMessage(message:String):void {
+ private function broadcastMessage(message:String):void {
for each (var screen:NetworkedScreen in settings.networkMap) {
if (screen != settings.thisScreen) {
send(screen, message);
@@ -276,7 +425,18 @@ package com.kitschpatrol.flashspan
}
}
- public function broadcastCertifiedMessage(message:String):void {
+ // sends a custom message to everyone, including self
+ public function broadcastCustomMessage(header:String, message:String = ''):void {
+ for each (var screen:NetworkedScreen in settings.networkMap) {
+ send(screen, CUSTOM_MESSAGE_HEADER + header + "," + message);
+ }
+ }
+
+ private function dispatchCustomMessageEvent(header:String, message:String):void {
+ this.dispatchEvent(new CustomMessageEvent(CustomMessageEvent.MESSAGE_RECEIVED, header, message));
+ }
+
+ private function broadcastCertifiedMessage(message:String):void {
for each (var screen:NetworkedScreen in settings.networkMap) {
if (screen != settings.thisScreen) {
sendCertified(screen, message);
@@ -284,6 +444,7 @@ package com.kitschpatrol.flashspan
}
}
+
// Convenience transmission functions
// Wrapped up for convenience
View
3  FlashSpanLibrary/src/com/kitschpatrol/flashspan/NetworkedScreen.as
@@ -8,6 +8,7 @@ package com.kitschpatrol.flashspan
public var screenHeight:int;
public var xOffset:int;
public var yOffset:int;
+ public var latency:int;
public var connected:Boolean;
@@ -22,7 +23,7 @@ package com.kitschpatrol.flashspan
if (this.hasOwnProperty(key)) {
this[key] = value;
- }
+ }
}
connected = false;
View
166 FlashSpanLibrary/src/com/kitschpatrol/flashspan/Random.as
@@ -0,0 +1,166 @@
+package com.kitschpatrol.flashspan
+{
+ /**
+ * Rndm by Grant Skinner. Jan 15, 2008
+ * Visit www.gskinner.com/blog for documentation, updates and more free code.
+ *
+ * Incorporates implementation of the Park Miller (1988) "minimal standard" linear
+ * congruential pseudo-random number generator by Michael Baczynski, www.polygonal.de.
+ * (seed * 16807) % 2147483647
+ *
+ *
+ *
+ * Copyright (c) 2008 Grant Skinner
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+ import flash.display.BitmapData;
+
+ // Provides common random functions using a seeded random system. Can be used through static interface or via instantiation.
+
+ public class Random {
+ // static interface:
+ // NOTE: for usage information, look at the instance methods below.
+
+ protected static var _instance:Random;
+ public static function get instance():Random {
+ if (_instance == null) { _instance = new Random(); }
+ return _instance;
+ }
+
+ public static function get seed():uint {
+ return instance.seed;
+ }
+ public static function set seed(value:uint):void {
+ instance.seed = value;
+ }
+
+ public static function get currentSeed():uint {
+ return instance.currentSeed;
+ }
+
+ public static function random():Number {
+ return instance.random();
+ }
+
+ public static function float(min:Number,max:Number=NaN):Number {
+ return instance.float(min,max);
+ }
+
+ public static function boolean(chance:Number=0.5):Boolean {
+ return instance.boolean(chance);
+ }
+
+ public static function sign(chance:Number=0.5):int {
+ return instance.sign(chance);
+ }
+
+ public static function bit(chance:Number=0.5):int {
+ return instance.bit(chance);
+ }
+
+ public static function integer(min:Number,max:Number=NaN):int {
+ return instance.integer(min,max);
+ }
+
+ public static function reset():void {
+ instance.reset();
+ }
+
+
+ // constants:
+ // private properties:
+ protected var _seed:uint=0;
+ protected var _currentSeed:uint=0;
+
+ // public properties:
+
+ // constructor:
+ public function Random(seed:uint=1) {
+ _seed = _currentSeed = seed;
+ }
+
+ // public getter/setters:
+
+ // seed = Math.random()*0xFFFFFF; // sets a random seed
+ // seed = 50; // sets a static seed
+ public function get seed():uint {
+ return _seed;
+ }
+ public function set seed(value:uint):void {
+ _seed = _currentSeed = value;
+ }
+
+ // gets the current seed
+ public function get currentSeed():uint {
+ return _currentSeed;
+ }
+
+ // public methods:
+ // random(); // returns a number between 0-1 exclusive.
+ public function random():Number {
+ return (_currentSeed = (_currentSeed * 16807) % 2147483647)/0x7FFFFFFF+0.000000000233;
+ }
+
+ // float(50); // returns a number between 0-50 exclusive
+ // float(20,50); // returns a number between 20-50 exclusive
+ public function float(min:Number,max:Number=NaN):Number {
+ if (isNaN(max)) { max = min; min=0; }
+ return random()*(max-min)+min;
+ }
+
+ // boolean(); // returns true or false (50% chance of true)
+ // boolean(0.8); // returns true or false (80% chance of true)
+ public function boolean(chance:Number=0.5):Boolean {
+ return (random() < chance);
+ }
+
+ // sign(); // returns 1 or -1 (50% chance of 1)
+ // sign(0.8); // returns 1 or -1 (80% chance of 1)
+ public function sign(chance:Number=0.5):int {
+ return (random() < chance) ? 1 : -1;
+ }
+
+ // bit(); // returns 1 or 0 (50% chance of 1)
+ // bit(0.8); // returns 1 or 0 (80% chance of 1)
+ public function bit(chance:Number=0.5):int {
+ return (random() < chance) ? 1 : 0;
+ }
+
+ // integer(50); // returns an integer between 0-49 inclusive
+ // integer(20,50); // returns an integer between 20-49 inclusive
+ public function integer(min:Number,max:Number=NaN):int {
+ if (isNaN(max)) { max = min; min=0; }
+ // Need to use floor instead of bit shift to work properly with negative values:
+ return Math.floor(float(min,max));
+ }
+
+ // reset(); // resets the number series, retaining the same seed
+ public function reset():void {
+ _seed = _currentSeed;
+ }
+
+ // private methods:
+ }
+
+}
View
10 FlashSpanLibrary/src/com/kitschpatrol/flashspan/Settings.as
@@ -9,14 +9,16 @@ package com.kitschpatrol.flashspan {
import flash.xml.XMLNode;
public class Settings extends Object {
- // A collection of settings. These can be set manually or are loaded from an INI file
+ // A collection of settings. These can be set manually or are loaded from an XML file
public static const SERVER_AUTO:String = "auto";
+ public static const SYNC_FRAMES:String = "frame";
+ public static const SYNC_TIME:String = "time";
- public var totalWidth:uint;
- public var totalHeight:uint;
+ public var totalWidth:int;
+ public var totalHeight:int;
public var scaleFactor:Number;
public var server:String;
-
+ public var syncMode:String;
public var thisScreen:NetworkedScreen; // reference...
public var networkMap:Vector.<NetworkedScreen>;
View
41 FlashSpanLibrary/src/com/kitschpatrol/flashspan/SpanSprite.as
@@ -0,0 +1,41 @@
+package com.kitschpatrol.flashspan {
+ import flash.display.Sprite;
+ import flash.events.MouseEvent;
+
+ public class SpanSprite extends Sprite {
+
+ public var totalWidth:int;
+ public var totalHeight:int;
+
+ public function SpanSprite(w:int, h:int, _totalWidth:int, _totalHeight:int, xOffset:int, yOffset:int, background:uint = 0xffffff) {
+ super();
+
+ totalWidth = _totalWidth;
+ totalHeight = _totalHeight;
+
+ // testing
+ this.graphics.beginFill(background);
+ this.graphics.drawRect(0, 0, totalWidth, totalHeight);
+ this.graphics.endFill();
+
+
+ // draw a unique rectangle in each section, for testing
+// var tempXOffset:int = 0;
+// while(tempXOffset < totalWidth) {
+// this.graphics.beginFill(Math.random() * uint.MAX_VALUE);
+// this.graphics.drawRect(tempXOffset, 0, w, h);
+// this.graphics.endFill();
+// tempXOffset += w;
+// }
+
+
+ // mask if needed?
+
+ // set offset TODO test this
+ this.x = -xOffset;
+ this.y = -yOffset;
+ }
+
+
+ }
+}
View
16 FlashSpanLibrary/src/com/kitschpatrol/flashspan/SyncEvent.as
@@ -1,16 +0,0 @@
-package com.kitschpatrol.flashspan
-{
- import flash.events.Event;
-
- public class SyncEvent extends Event
- {
- public static var staticFrameCount:uint;
- public var frameCount:uint;
-
- public function SyncEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false) {
- SyncEvent.staticFrameCount++;
- frameCount = SyncEvent.staticFrameCount;
- super(type, bubbles, cancelable);
- }
- }
-}
View
16 FlashSpanLibrary/src/com/kitschpatrol/flashspan/events/CustomMessageEvent.as
@@ -0,0 +1,16 @@
+package com.kitschpatrol.flashspan.events {
+ import flash.events.Event;
+
+ public class CustomMessageEvent extends Event {
+ public static const MESSAGE_RECEIVED:String = "messageReceived";
+
+ public var header:String;
+ public var message:String;
+
+ public function CustomMessageEvent(type:String, header:String, message:String, bubbles:Boolean=false, cancelable:Boolean=false) {
+ this.header = header;
+ this.message = message;
+ super(type, bubbles, cancelable);
+ }
+ }
+}
View
12 FlashSpanLibrary/src/com/kitschpatrol/flashspan/events/FlashSpanEvent.as
@@ -0,0 +1,12 @@
+package com.kitschpatrol.flashspan.events {
+ import flash.events.Event;
+
+ public class FlashSpanEvent extends Event {
+ public static const START:String = "start";
+ public static const STOP:String = "stop";
+
+ public function FlashSpanEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false) {
+ super(type, bubbles, cancelable);
+ }
+ }
+}
View
14 FlashSpanLibrary/src/com/kitschpatrol/flashspan/events/FrameSyncEvent.as
@@ -0,0 +1,14 @@
+package com.kitschpatrol.flashspan.events {
+ import flash.events.Event;
+
+ public class FrameSyncEvent extends Event {
+ public static const SYNC:String = "frameSync";
+
+ public var frameCount:uint;
+
+ public function FrameSyncEvent(type:String, frameCount:uint, bubbles:Boolean=false, cancelable:Boolean=false) {
+ this.frameCount = frameCount;
+ super(type, bubbles, cancelable);
+ }
+ }
+}
View
14 FlashSpanLibrary/src/com/kitschpatrol/flashspan/events/TimeSyncEvent.as
@@ -0,0 +1,14 @@
+package com.kitschpatrol.flashspan.events {
+ import flash.events.Event;
+
+ public class TimeSyncEvent extends Event {
+ public static const SYNC:String = "timeSync";
+
+ public var time:int;
+
+ public function TimeSyncEvent(type:String, time:int, bubbles:Boolean=false, cancelable:Boolean=false) {
+ this.time = time;
+ super(type, bubbles, cancelable);
+ }
+ }
+}
View
3  build-resources/settings-template.xml
@@ -3,7 +3,8 @@
<totalWidth>640</totalWidth>
<totalHeight>480</totalHeight>
<scaleFactor>1</scaleFactor>
- <server>auto</server>
+ <server>auto</server> <!-- auto or a number -->
+ <syncMode>frame</syncMode> <!-- time or frame -->
<networkMap>
<!-- Filled in with settings-screen-template.xml -->
</networkMap>
View
6 build.properties
@@ -14,8 +14,8 @@ EXAMPLE_APP_DEBUG_DIR = ${basedir}/example-debug
# Test Settings, always span horizontally
MULTI_EXAMPLE_APP_DEBUG_DIR = ${basedir}/example-debug-multi
MULTI_EXAMPLE_APP_RELEASE_DIR = ${basedir}/example-release-multi
-SCREEN_COUNT = 2
-TOTAL_WIDTH = 1680
-TOTAL_HEIGHT = 1000
+SCREEN_COUNT = 3
+TOTAL_WIDTH = 800
+TOTAL_HEIGHT = 600
SCALE_FACTOR = 1
STARTING_PORT = 63644
View
8 build.xml
@@ -150,14 +150,14 @@
</xmltask>
<!-- Copy over the base template -->
- <xmltask source="${basedir}/build-resources/settings-template.xml" dest="${exampleAppOutputDir}/settings.xml">
+ <xmltask source="${basedir}/build-resources/settings-template.xml" dest="${exampleAppOutputDir}/flash_span_settings.xml">
<!-- Clear the comment placeholder -->
<remove path="//child::comment()"/>
</xmltask>
<antcall target="add-screen-settings">
<param name="screenID" value="0"/>
- <param name="destination" value="${exampleAppOutputDir}/settings.xml"/>
+ <param name="destination" value="${exampleAppOutputDir}/flash_span_settings.xml"/>
</antcall>
</target>
@@ -218,7 +218,7 @@
<!-- Clear the initial screen setting, going to be replaced below -->
- <!-- Revise and put the settings.xml into a buffer -->
+ <!-- Revise and put the flash_span_settings.xml into a buffer -->
<xmltask source="${basedir}/build-resources/settings-template.xml" dest="${MULTI_EXAMPLE_APP_DEBUG_DIR}/settings-temp.xml" failWithoutMatch="true">
<!-- Clear the comment placeholder -->
<remove path="//child::comment()"/>
@@ -252,7 +252,7 @@
<math result="xOffset" operand1="${screenWidth}" operation="*" operand2="@{i}" datatype="int" />
<!-- Copy and change the settings xml -->
- <xmltask source="${MULTI_EXAMPLE_APP_DEBUG_DIR}/settings-temp.xml" dest="${thisScreenDir}/settings.xml" />
+ <xmltask source="${MULTI_EXAMPLE_APP_DEBUG_DIR}/settings-temp.xml" dest="${thisScreenDir}/flash_span_settings.xml" />
<!-- Copy and revise app file to have unique ID, title, and tiled position on screen -->
<xmltask source="${EXAMPLE_APP_DEBUG_DIR}/${EXAMPLE_APP_NAME}-app.xml" dest="${thisScreenDir}/${EXAMPLE_APP_NAME}-app.xml" failWithoutMatch="true">
Please sign in to comment.
Something went wrong with that request. Please try again.