Backbone.UndoをTypescriptで使ってみた

backbone.undo.jsというBackboneJS用のUNDOモジュールが有ります。

http://backbone.undojs.com/

こちらをTypescriptで使用してみました。うまくいったのでサンプルを掲載しておきます

通常、Typescriptで既存のライブラリを使用する際には、Typescript用に型定義ファイルを作成してやる必要が有ります。

世の中にはいい人がいて、たいていのライブラリは型定義されていたりします。

https://github.com/borisyankov/DefinitelyTyped

しかし、今回使用するbackbone.undo.jsはまだこちらでは定義されていませんでしたので、適当に作成してみました。

https://github.com/anagotan/DefinitelyTyped/tree/master/backbone.undo

これを利用してUndo機能を実装してみます。

仕様としては、addボタンを押すと「name=0,value=0」のボタンが追加されていきます。update_nameおよびupdate_valueのボタンでランダムな数値に置き換わります。

これをもとにundoおよびredoを実装してみました。

  • index.html




		
		




		



  • app.ts
/// 
/// 
/// 
/// 

module sample {
		var undoButton = $("#undo");
		var redoButton = $("#redo");
		var addButton = $("#add");
		var updateNameButton = $("#update_name");
		var updateValueButton = $("#update_value");

		export class App {
				private listview: ItemListView;
				constructor() {

						this.listview = new ItemListView({
								"collection": new ItemCollection([
										new ItemModel({ "name": "0" ,"value":"0"}) ,
								])
						});
						var self = this;
						var manager = new Backbone.UndoManager({ register: [this.listview.collection], track: true });

						manager.removeUndoType("change:isChanging");
						manager.addUndoType("change:isChanging", {
								"on": function (model, isChanging, options) {
										console.log("manager.addUndoType.on");
								},
								"undo": function (model, before, after, options) {
										console.log("manager.addUndoType.undo");
										model.set(before)
								},
								"redo": function (model, before, after, options) {
										console.log("manager.addUndoType.redo");
										model.set(after)
								}
						});

						manager.on("all", function (type) {
								console.log("all.type:" + type);
								switch (type) {
										case "undo": {
												console.log("undo");
												break;
										}
										case "redo": {
												console.log("redo");
												break;
										}
								}
						});
						manager.trigger("undo redo");

						manager.stack.on("add", function () {
								console.log("stack.on.add");
						});

						undoButton.click(function () {
								console.log("undoButton.click");
								manager.undo(true);
						});
						redoButton.click(function () {
								console.log("redoButton.click");
								manager.redo(true);
						});

						addButton.click(function () {
								console.log("addButton.click");

								self.listview.collection.add(new ItemModel({ "name": "0", "value": "0" }));
						});

						updateNameButton.click(function () {
								console.log("updateNameButton.click");
								self.listview.collection.forEach(function (item) {
										item.set("name", _.random(100).toString());
								});
						});
						updateValueButton.click(function () {
								console.log("updateValueButton.click");
								self.listview.collection.forEach(function (item) {
										item.set("value", _.random(100).toString());
								});
						});

				}

				draw(): void {

						this.listview.render();
				}
		}

		class ItemModel extends Backbone.Model {
				constructor(options?) {
						if (options) {
								if (options.name) this.set({ "name": options.name }, { "validate": true });
								if (options.value) this.set({ "value": options.value }, { "validate": true });
						}
						super(options);
						this.bind("change", this.change);
				}
				change(): void {
						console.log("ItemModel.change:name="+this.get("name")+",value="+this.get("value"));
				}
				defaults() {
						return {
								"id": _.uniqueId(),
								"name": "",
								"value":"" 
						}
				}
				validate(attrs) {
						if (!attrs.name || _.isEmpty(attrs.name)) {
								return "name must not be empty";
						}
						if (!attrs.value || _.isEmpty(attrs.value)) {
								return "value must not be empty";
						}
				}
		}

		class ItemCollection extends Backbone.Collection{
				constructor(options) {
						super(options);
						this.on("change", this.onchange, this);
						this.on("add", this.onchange, this);
				}
				onchange(model,value,options):void {
						console.log("ItemCollection.change");
				}
		}

		class ItemView extends Backbone.View {
				template: (data: any) => string;
				constructor(options?) {
						var html = $("#template-item").html();
						this.template = _.template(html);
						this.events = {
								"click": "onclick",
								"change":"onchange" 
						};
						super(options);
						this.model.bind("change", this.render, this);
				}
				private onclick() {
						console.log("ItemView.onclick");
				}
				private change() {
						console.log("ItemView.onchange");
						this.render();
				}

				render(): ItemView {
						var html = this.template(this.model.toJSON());
						$(this.el).html(html);
						return this;
				}

		}

		class ItemListView extends Backbone.View{
			 // list: ItemCollection;
				constructor(options?) {
						this.el = "#item-list";
						if (options) {
								if (options.collection) this.collection = options.collection;
						}
						super(options);
						//_.bindAll(this);
						this.listenTo(this.collection, 'add', this.render);
						this.listenTo(this.collection, 'remove', this.render);
						this.listenTo(this.collection, 'reset', this.render);
				}
				render(): ItemListView {
						var el = $(this.el);
						el.empty();
						this.collection.forEach(item=> {
								console.log("ItemListView.render");
								var view=new ItemView({ "model": item }).render();
								el.append(view.el);
						});
						return this;
				}
		}
}

new sample.App().draw();

実行する際には別途,jquery,underscore.js,backbone.js を用意しておく必要が有ります

scalaのRedisクライアントbrandoを使ってみる(その3)

PlayFramework2.2でscalaのRedisクライアントbrandoを使用する際にはちょっと注意が必要です。

brando1.0はakka2.3対応らしく、Play2.2ではまだakka2.2の対応となっているためそのままでは動きません。

仕方がないのでbrando0.3にバージョンを落として使用します。

また前回のサンプルだと、キーに値がない場合にはエラーになってしまいます。

そこでbrando0.3を少し改造します

  • brand.Brando.scala
		case Tcp.Received(data) ⇒
			parseReply(data) { reply ⇒
				reply match {
					case Some(List(Some(x: ByteString), Some(channel: ByteString), Some(message: ByteString))) if (x.utf8String == "message") ⇒

						val pubSubMessage = PubSubMessage(channel.utf8String, message.utf8String)
						getSubscribers(channel).map { x ⇒
							x ! pubSubMessage
						}
//ここから
					case None =>
						requesterQueue.dequeue ! Option(ByteString(""))
//ここまで追加
					case _ ⇒
						requesterQueue.dequeue ! (reply match {
							case Some(failure) if failure.isInstanceOf[Status.Failure] ⇒ failure
							case success ⇒ success
						})
				}
			}

これで使いやすくなりました

scalaのRedisクライアントbrandoを使ってみる(その2)

前回brandoを使用してRedisの値をGETできたので今回はSubscribeしてみます。

Redisサーバ側から値をプッシュし、それをPlayのWebsocketでブラウザ側に流すため用に特化しています

試行錯誤をかなりしましたが意外と単純でした。

ポイントは起動するクラスをActorとして作成し、PubSubMessageをReceiveするだけです


import akka.actor._
import akka.util._
import scala.concurrent._
import brando._


class MyPubSub extends Actor{
		implicit val timeout= Timeout(5000)
		val system=ActorSystem("brando")
		val brando = system.actorOf(Brando("localhost", 6379,None,None))	 

		brando ! Request("SUBSCRIBE","key")
		def receive={
			 case msg:PubSubMessage=>println("msg="+msg)
			 case _ => 
		 }
}

object PubSub {
	def main(args: Array[String]): Unit = {
		val system=ActorSystem("a")
		system.actorOf(Props(new MyPubSub()))
	}
}

これだけでした。

scalaのRedisクライアントbrandoを使ってみる

ちょっとはまったのでメモ。

scalaのRedisクライアントには何種類か有ります。

http://redis.io/clients

当初scala-redisを使っていたのですが、Pub/Subする際に、内部的にスレッドを生成しているようで、大量のPub/Subを作成したい場合(数千)にはOutOfMemoryで落ちてしまいます。

そこでActorモデルのbrandoを使用してみました。

ネット上にはほとんど情報もなくちょっとはまってしまったので記録しておきます

https://github.com/chrisdinn/brando

こちらからダウンロードしてsbt packageでJarを作成します。

あとakkaが必要なので2.3をダウンロードしておきます。

いろいろと試行錯誤し以下のコードでとれました

Pub/Subとかこれから確かめます

package brandoTest

import akka.actor._
import akka.util._
import akka.pattern.ask
import scala.concurrent._
import brando._

object Main {

	def main(args: Array[String]): Unit = {
		implicit val timeout= Timeout(5000)
		val system=ActorSystem("brando")
		val brando = system.actorOf(Brando("localhost", 6379,None,None))
		val future=brando.ask (Request("GET","key")).mapTo[Some[ByteString]]
		val res=Await.result(future,timeout.duration)
		res match{
			case Some(x)=>println(x.decodeString("UTF-8"))
		}

	}
}

C言語でStringBufferもどきを作成してみる

c言語は速いのはいいのですが、いかんせん標準ライブラリ群が貧弱な面は否めません。

文字列周りの処理だとC++のSTLあたりを使用すれば簡単ですが、純粋にCで簡単に作成してみました

  • string_buffer.h
#ifndef _STRING_BUFFER_H
#define _STRING_BUFFER_H
#include 

struct string_buffer {
	unsigned int item_size;
	char *item;
	unsigned int max_item_size; // 実際のメモリ確保量
};


extern struct string_buffer *create_string_buffer();
extern void delete_string_buffer(struct string_buffer *buf);
extern int append_string_buffer(struct string_buffer *buf, char *val, unsigned int val_len);


#endif
  • string_buffer.c
#include "string_buffer.h"
#define ITEM_SIZE 1024
struct string_buffer *create_string_buffer(){
				struct string_buffer *buf;
				buf=(struct string_buffer *)malloc(sizeof(struct string_buffer));
				if(buf==NULL)return NULL;
				buf->item=(char*)malloc(sizeof(char)*ITEM_SIZE);
				if(buf->item==NULL){
								free(buf);
								return NULL;
				}
				buf->item[0]='\0';
				buf->item_size=0;
				buf->max_item_size=ITEM_SIZE;
				return buf;
}
void delete_string_buffer(struct string_buffer *buf){
				if(buf!=NULL){
								if(buf->item!=NULL) free(buf->item);
								free(buf);
				}
}
int append_string_buffer(struct string_buffer *buf, char *val, unsigned int val_len){
				int size;
				char *newbuf;
				int diff=buf->max_item_size-buf->item_size;
				if(diff>val_len){
								strncat(buf->item,val,val_len);
								buf->item_size+=val_len;
				}else{
								size=buf->max_item_size+ITEM_SIZE;
								newbuf=(char*)malloc(sizeof(char)*size);
								if(newbuf==NULL) return -1;
								newbuf[0]='\0';
								strncpy(newbuf,buf->item,buf->item_size);
								free(buf->item);
								buf->item=newbuf;
								buf->max_item_size=size;
								return append_string_buffer(buf,val,val_len);
				}
				return 0;
}

実行してみます

  • main.c

static void print_string_buffer(char *header,struct string_buffer *buf){
				printf("%s:item_size=%d,char=%s,max_item_size=%d\n",header,buf->item_size,buf->item,buf->max_item_size);
}
int main(){
				struct string_buffer *buf;
				buf=create_string_buffer();
				if(buf==NULL)exit(0);

				append_string_buffer(buf,"test",sizeof("test"));
				print_string_buffer("1",buf);

				append_string_buffer(buf," 1111",sizeof(" 1111"));
				print_string_buffer("2",buf);

				append_string_buffer(buf,"",sizeof(""));
				print_string_buffer("3",buf);

				append_string_buffer(buf,"a",sizeof("a"));
				print_string_buffer("4",buf);

				append_string_buffer(buf,"12345678901234567890",12);
				print_string_buffer("5",buf);

				append_string_buffer(buf,"123",sizeof("123"));
				print_string_buffer("6",buf);
				delete_string_buffer(buf);
}
  • 実行結果
$ a.out
1:item_size=5,char=test,max_item_size=1024
2:item_size=11,char=test 1111,max_item_size=1024
3:item_size=12,char=test 1111,max_item_size=1024
4:item_size=14,char=test 1111a,max_item_size=1024
5:item_size=26,char=test 1111a123456789012,max_item_size=1024
6:item_size=30,char=test 1111a123456789012123,max_item_size=1024

StringからEnumの作成

文字列からEnum型へ変換するサンプルです

public class test{ 
 
	public static enum Channel{PC,WEB; 
		static Channel get(String s){ 
			Channel[] channels=Channel.values(); 
			for(Channel channel:channels){ 
				if(channel.toString().equals(s))return channel; 
			} 
			return null; 
		} 
}; 
 
	public static void main(String[] argv){ 
		System.out.println(Channel.WEB); // Enumをそのまま出力
		System.out.println(Channel.get("PC")); // 文字列からEnumを取得してみる
	} 
} 

Linuxでミリ秒まで取得

ansiのC言語で用意されているtime関数では現在時刻をミリ秒まで取得できません。そこでLinuxならではの取得方法の覚え書きです

gettimeofday関数を用いてミリ秒を取得します

以下のサンプルではミリ秒単位で現在時刻を取得し、2つの時間の差を求めています

#include
#include
#include
#include
#include

struct my_tm {
	time_t tim; // yyyymmddhhmmss
	long msec;	// milli sec
};

static struct my_tm *get_now_tm(){
	struct my_tm *qt;
	struct tm *tmp;
	struct timeval tv;

	qt=(struct my_tm*)malloc(sizeof(struct my_tm));
	if(qt == NULL)return NULL;
	gettimeofday(&tv,NULL);
	tmp=localtime(&tv.tv_sec);
	qt->tim=mktime(tmp);
	qt->msec=tv.tv_usec/1000;
	printf("%04d/%02d/%02d %02d:%02d:%02d:%3d\n",
		tmp->tm_year + 1900, tmp->tm_mon + 1,
		tmp->tm_mday, tmp->tm_hour,
		tmp->tm_min, tmp->tm_sec,
		tv.tv_usec/1000);
	return qt;
}

// return milli second
static inline long my_tm_cmptime(struct my_tm* now_t,struct my_tm* prev_t){
	long diff_sec;
	long diff_msec;
	diff_sec=difftime(now_t->tim,prev_t->tim);
	diff_msec=now_t->msec-prev_t->msec;
	return diff_sec*1000+diff_msec;
}

int main(){
	int i,b;
	struct my_tm *q1;
	struct my_tm *q2;
	long dif;

	q1=get_now_tm();
	sleep(2); // 2秒あける
	b=0;
	for(i=0;i<1000000;i++) b++; // +αあける
	q2=get_now_tm();
	dif=my_tm_cmptime(q2,q1);
	printf("%d\n",dif);

	free(q1);
	free(q2);
}

PlayFramework+Redis+websocketでプッシュ配信(データ圧縮編)

Websocketに最近はまっています。

前回プッシュ配信をRedisのPub/Subを用いて実現した訳ですが、データ量が増えてくるとどうしても配信するデータを圧縮したくなります。

特にAWSなんかだと、従量課金となっていますので、データ転送量は少なければ少ないほど○

Websocket自体はまだデータの圧縮はサポートされていないとのことですので、自前でデータの圧縮伸長を実装してみました。

また、それだけではつまらないので、Reidsにチャンネルを作成し、ReidsのチャンネルごとにPub/Subする仕組みを入れています

図解するとちょっとわかりにくいですがこんな感じ

			user1							userActor			 redisActor	 sub		Redis
		|code1,code2| ---	|	 user1	 | ---	|	code1	| --- |
											 |					 |			|				 |
			user2						|					 | ---	|	code2	| --- |
		|code2,code3| ---	|	 user2	 | ---	|	code3	| --- |

User別に作成したActorと、ユーザが選択したチャンネル(code1,code2,code3)別に作成したActorを別々に作成し、チャンネルがReidsから更新された際には、購読しているUserのActorへ更新をかけるという感じです

こうすることにより、チャンネル数がユーザ数より圧倒的に少ない場合には、Reidsに対するコネクションも削減でき、効率が良くなります。

  • route
GET		 /board													 controllers.Application.board(code:Option[String])	 # 画面作成用
GET		 /board/data											controllers.Application.data(code) #WSでデータ取得用
GET		 /asset/javascripts/board.js		 #Javascript controllers.Application.boardJs(code:String)
  • Compress.scala

データ圧縮用に作成します。String型をZIP圧縮し、BASE64でエンコードします

package models

import java.util.zip._
import java.io._
import org.apache.commons.codec.binary._

object Compress {
	def encode(str:String):String={
		val out = new ByteArrayOutputStream()
		val defl = new DeflaterOutputStream(out, new Deflater(Deflater.BEST_COMPRESSION, true))
		defl.write(str.getBytes())
		defl.close()
		val compressed = Base64.encodeBase64(out.toByteArray())
		new String(compressed)		
	}
}
  • UserActor.scala
...
	
	def notifyAll(code:String,data:String){
		// 実際にWebSocketでブラウザに送るデータ
		val msg=JsObject(
				Seq(
						"code"->JsString(code),
						"data"->JsString(Compress.encode(data)) // JSONの一部データ部分のみ圧縮
..
  • Javascriptで解凍

JavaScriptでZIP解凍、Base64でコードするために↓からinfrate.js,base64.js,utf.jsなどをダウンロードしておきます。

http://www.onicos.com/staff/iz/amuse/javascript/expert/

@(code:String)(implicit r: RequestHeader)

$(function() {

		var WS = window['MozWebSocket'] ? MozWebSocket : WebSocket
		var socket = new WS("@routes.Application.data(code).webSocketURL()")

		
		var receiveEvent = function(event) {
				var data = JSON.parse(event.data)

				var bs=base64decode(data.data) // Base64デコード
				var dec=zip_inflate(bs)				// ZIP解凍
				var p=JSON.parse(dec)
				$("#data").text(p.data);
			 
		}

		socket.onmessage = receiveEvent

})

サンプルの一式は以下においておきました

https://github.com/anagotan/play_websocket_compressed

PlayFramework+Redis+websocketでプッシュ配信を作成してみる

Websocketを使ったサンプルだとチャットが多いのですが、実際にはチャットのアプリを作ることはあまり有りません。

どちらかというとサーバ側から配信するというような用途が多いのではないでしょうか?

PlayframeworkのサンプルにはWebsocketを使ったサンプルが付属しています。

これを改造してサーバ側から配信できるように改造してみました

  • 環境
    • MacOS10.9
    • jdk1.7
    • playframework2.2
    • redis2.8.4
    • scala2.10
  • playframeworkのチャットサンプル

brewでインストールした場合にはこちらに入っているので、作業エリアにコピーしておきます

/usr/local/Cellar/play/2.2.1/libexec/samples/scala/websocket-chat/

  • unicast

一斉配信したい場合にはサンプルで採用されているbroadcastをそのまま使用してもいいのですが、今回は一対一通信で配信したいのでunicastを使用します。サンプルはこちらを参考にしました。

http://satoshi-m8a.github.io/blog/2013/05/18/scala-concurrent-unicast/

  • build.sbt

scalaからredisに接続するためにはscala-redisライブラリを使用します。そのためbuild.sbtに以下を記述します

import play.Project._

name := "websocket-chat"

version := "1.0"

libraryDependencies ++= Seq(
	cache,
	"net.debasishg" % "redisclient_2.10" % "2.11"
)

playScalaSettings
  • conf/logger.xml

ログ出力用に追加します


		
	
	
	
		 ${application.home}/logs/application.log
		 
			 %date - [%level] - from %logger in %thread %n%message%n%xException%n
		 
	 

	
		
			%coloredLevel %d{HH:mm:ss.SSS} [%thread] %logger{15} - %message%n%xException{5}
		
	
	
	
	

	
		
		
	
	

  • conf/application.conf

redisの接続先をconfに書いておきます


redis.uri="http://localhost:6379/"
  • models/ChatRoom.scala

モデルをscala-redisのテストコードを参考に修正します

package models

import akka.actor._
import scala.concurrent.duration._

import play.api._
import play.api.libs.json._
import play.api.libs.iteratee._
import play.api.libs.concurrent._

import akka.util.Timeout
import akka.pattern.ask

import play.api.Play.current
import play.api.libs.concurrent.Execution.Implicits._
import play.api.libs.iteratee.Concurrent.Channel

import com.redis._
import akka.actor.{ Actor, ActorSystem, Props }
case class PublishMessage(channel: String, message: String)
case class SubscribeMessage(channels: Array[String])
case class UnsubscribeMessage(channels: Array[String])
case object GoDown



object Robot {

	def apply(chatRoom: ActorRef) {

		// Create an Iteratee that logs all messages to the console.
		val loggerIteratee = Iteratee.foreach[JsValue](event => Logger("robot").info(event.toString))

		implicit val timeout = Timeout(1 second)
		// Make the robot join the room
		chatRoom ? (Join("Robot")) map {
			case Connected(robotChannel) =>
				// Apply this Enumerator on the logger.
				robotChannel |>> loggerIteratee
		}

		// Make the robot talk every 30 seconds
		Akka.system.scheduler.schedule(
			30 seconds,
			30 seconds,
			chatRoom,
			Talk("Robot", "I'm still alive")
		)
	}

}

object ChatRoom {

	implicit val timeout = Timeout(1 second)

	// username split ","	ex. "6758,9997"
	def join(username: String): scala.concurrent.Future[(Iteratee[JsValue, _], Enumerator[JsValue])] = {
		val roomActor = Akka.system.actorOf(Props[ChatRoom])
		
		roomActor ! SubscribeMessage(username.split(","))

		Robot(roomActor)

		(roomActor ? Join(username)).map {

			case Connected(enumerator) =>

				// Create an Iteratee to consume the feed
				val iteratee = Iteratee.foreach[JsValue] {
					event =>
						roomActor ! Talk(username, (event \ "text").as[String])
				}.mapDone {
					_ =>
						roomActor ! Quit(username)
				}

				(iteratee, enumerator)

			case CannotConnect(error) =>

				// Connection error

				// A finished Iteratee sending EOF
				val iteratee = Done[JsValue, Unit]((), Input.EOF)

				// Send an error and close the socket
				val enumerator = Enumerator[JsValue](JsObject(Seq("error" -> JsString(error)))).andThen(Enumerator.enumInput(Input.EOF))

				(iteratee, enumerator)

		}
	}
}

class ChatRoom extends Actor {
	println("starting subscription service ..")
	val system = ActorSystem("sub")
	val uri = new java.net.URI(Play.configuration.getString("redis.uri").get)
	val r = new RedisClient(uri.getHost,uri.getPort)
	val s = system.actorOf(Props(new Subscriber(r)))
	s ! Register(callback)
	
	def receive = {
		case SubscribeMessage(chs) => sub(chs)
		case UnsubscribeMessage(chs) => unsub(chs)
		case GoDown =>
			r.quit
			system.shutdown()
			system.awaitTermination()

		//case x => println("Got in Sub " + x)
		
		
		
		case Join(username) => {
			sender ! Connected(chatEnumerator)
		}

		case NotifyJoin(username) => {
			notifyAll("join", username, "has entered the room")
		}

		case Talk(username, text) => {
			notifyAll("talk", username, text)
		}

		case Quit(username) => {
			notifyAll("quit", username, "has left the room")
		}
	}

	def sub(channels: Array[String]) = {
		s ! Subscribe(channels.toArray)
	}

	def unsub(channels: Array[String]) = {
		s ! Unsubscribe(channels.toArray)
	}

	def callback(pubsub: PubSubMessage) = pubsub match {
		case E(exception) => println("Fatal error caused consumer dead. Please init new consumer reconnecting to master or connect to backup")
		case S(channel, no) => println("subscribed to " + channel + " and count = " + no)
		case U(channel, no) => println("unsubscribed from " + channel + " and count = " + no)
		case M(channel, msg) =>
			msg match {
				// exit will unsubscribe from all channels and stop subscription service
				case "exit" =>
					println("unsubscribe all ..")
					r.unsubscribe

				// message "+x" will subscribe to channel x
				case x if x startsWith "+" =>
					val s: Seq[Char] = x
					s match {
						case Seq('+', rest @ _*) => r.subscribe(rest.toString){ m => }
					}

				// message "-x" will unsubscribe from channel x
				case x if x startsWith "-" =>
					val s: Seq[Char] = x
					s match {
						case Seq('-', rest @ _*) => r.unsubscribe(rest.toString)
					}

				// other message receive
				case x =>
					println("received message on channel " + channel + " as : " + x)
					notifyAll("talk", channel, x)
			}
	}
	

	var chatChannel: Option[Channel[JsValue]] = None

	def onStart: Channel[JsValue] => Unit = {
		channel =>
			chatChannel = Some(channel)
			println("start")
			self ! NotifyJoin("you")
	}

	def onError: (String, Input[JsValue]) => Unit = {
		(message, input) =>
			println("onError " + message)
	}

	def onComplete = println("onComplete")

	val chatEnumerator = Concurrent.unicast[JsValue](onStart, onComplete, onError)
/*
	def receive = {

		case Join(username) => {
			sender ! Connected(chatEnumerator)
		}

		case NotifyJoin(username) => {
			notifyAll("join", username, "has entered the room")
		}

		case Talk(username, text) => {
			notifyAll("talk", username, text)
		}

		case Quit(username) => {
			notifyAll("quit", username, "has left the room")
		}

	}
	* 
	*/

	def notifyAll(kind: String, user: String, text: String) {
		val msg = JsObject(
			Seq(
				"kind" -> JsString(kind),
				"user" -> JsString(user),
				"message" -> JsString(text)
			)
		)
		chatChannel match {
			case Some(channel) => channel.push(msg)
			case _ => println("nothing")
		}
	}
}

case class Join(username: String)
case class Quit(username: String)
case class Talk(username: String, text: String)
case class NotifyJoin(username: String)
case class Connected(enumerator: Enumerator[JsValue])
case class CannotConnect(msg: String)

これで準備完了

  • 実行

play runで実行し、ユーザ名部分にカンマ区切りでキーを入力します。このキーはカンマ区切りで複数入力可能で、このキーがredisのキーとなります。

play run

画面が起動したら、a,b でログインしてみます

その後Reidsのコマンドで値を送ってみます

redis-cli publish a test

画面にa test が表示されます

CocoaのFrameworkでSTLを使ってみる

最近Cocoaにはまっています。Windows用に作成したライブラリをMacへ移植してみたのですが意外とはまったのでメモ

環境

  • MacOX10.9
  • XCode 5

TestAppという単純なアプリを作成しその中からSTLで作成したFrameworkを呼ぶことにするサンプルです。

UtilsというなのFramework

まず、ライブラリの作成です。XCodeでOSX用のCocoaFrameworkを作成します。

作成した後にファイルの追加で以下のヘッダファイルとC++ファイルを追加します

  • Test.h
#ifndef Utils_Test_h
#define Utils_Test_h
#include 
// 値を保持するクラス
class Bean{
public:
		int a;
		int b;

		Bean();
		void set(int a,int b);
		std::string to_s();
};
// 値を操作するクラス
class Test{
public:
		int add(int a,int b);
		std::shared_ptr add(std::shared_ptr a ,std::shared_ptr b);
};
#endif
  • Test.cpp
#include "Test.h"
#include 
Bean::Bean(){
		a=0;
		b=0;
}
void Bean::set(int a_,int b_){
		a=a_;
		b=b_;
}
std::string Bean::to_s(){
		char buf[1000];
		sprintf(buf,"a=%d,b=%d",a,b);
		return std::string(buf);
}
int Test::add(int a, int b){
		return a+b;
}
std::shared_ptr Test::add(std::shared_ptr a ,std::shared_ptr b){
		std::shared_ptr ret=std::shared_ptr(new Bean());
		int va=a->a+b->a;
		int vb=b->b+b->b;
		ret->set(va,vb);
		return ret;
}

ついでにObjective-Cのクラスも追加します。こちらを参考に

  • Utils.h
#import 

@interface Utils : NSObject
+(NSString*)addBrackets:(NSString*)string;
@end
  • Utils.m
#import "Utils.h"

@implementation Utils
+(NSString *)addBrackets:(NSString *)string
{
		return [NSString stringWithFormat:@"[-- %@ --]", string];
}
@end

それぞれのヘッダファイルをPublickにしたのちに、コンパイルするのですが、Build Rulesの Apple LLVM 5.0 Language C++のC++ Language Dialect を -std=c++11へ、C++ Standard Libraryを libc++(LLVM C++ standard library with C++ 11 supportへ変更します。また Apple LLVM 5.0 Language のCompile Source As をObjective-C++へと変更しておきます。

これでコンパイルOKのはず

TestAppというアプリ作成

OSXのApplicationのCocoaApplicationからプロジェクトを作成し、テキストボックスとボタンを配置しておきます。また、プロジェクトのFrameworksに先ほど作成したUtilsのフレームワークを追加しておきます。

  • AppDelegate.h
#import 

@interface AppDelegate : NSObject 

@property (assign) IBOutlet NSWindow *window;

@property IBOutlet NSButton *button;
@property IBOutlet NSTextField *text;

-(IBAction)pushButton:(id)sender;
@end
  • AppDelegate.m
#import "AppDelegate.h"

#import 
#import 
#include 

@implementation AppDelegate
@synthesize button;
@synthesize text;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
		// Insert code here to initialize your application
}

-(IBAction)pushButton:(id)sender{
		std::shared_ptr a=std::shared_ptr(new Bean());
		std::shared_ptr b=std::shared_ptr(new Bean());
		std::shared_ptr t=std::shared_ptr(new Test());

		a->set(1,2);
		b->set(10,20);
		std::shared_ptr tt=t->add(a,b);
		NSString* swk=[NSString stringWithCString:tt->to_s().c_str() encoding:[NSString defaultCStringEncoding]];
		NSString* str=[Utils addBrackets:swk];
		[text setStringValue:str];
}
@end

先ほどのライブラリと同様にBuildSettingを変更しておきます。

これでコンパイルとリンクがOKのはず。。

ボタンを押すとライブラリで計算した値がテキストボックスに出るはずです