1 module evael.network.ClientSocket;
2 
3 import std.socket;
4 import std.string : format;
5 import std.array : insertInPlace;
6 import std.exception : enforce;
7 
8 import core.thread;
9 
10 public import evael.network.Packet;
11 
12 import msgpack;
13 
14 class ClientSocket
15 {
16 	enum PACKET_SIZE = 4096;
17 
18 	private alias void delegate(ref Packet) OnDataEvent;
19 	private alias void delegate() OnDisconnectedEvent;
20 	
21 	private OnDataEvent m_onDataEvent;
22 	private OnDisconnectedEvent m_onDisconnectedEvent;
23 	
24 	/// Socket
25 	private Socket m_socket;
26 	
27 	/// Main thread
28 	private Thread m_thread;
29 	
30 	private bool m_connected;
31 	
32 	@nogc @safe
33 	public this() pure nothrow
34 	{
35 		this.m_connected = false;
36 	}
37 	
38 	@nogc @safe
39 	public this(Socket socket) pure nothrow
40 	{
41 		this.m_socket = socket;
42 		this();
43 	}
44 	
45 	/**
46 	 * Connects to a server.
47 	 * Params:
48 	 * 		 ip : server ip address
49 	 * 		 port : server port
50 	 */
51 	public void connect(in string ip, in ushort port)
52 	{
53 		assert(!this.m_connected, "Client is already connected to a server.");
54 		
55 		this.m_socket = new Socket(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP);
56 		this.m_socket.blocking(true);
57 		this.m_socket.connect(new InternetAddress(ip, port));
58 		
59 		enforce(this.m_socket.isAlive(), format("Unable to connect to server %s:%d.", ip, port));
60 		
61 		this.m_connected = true;
62 		
63 		this.m_thread = new Thread(&this.waitForData);
64 		this.m_thread.start();
65 	}
66 	
67 	/**
68 	 * Main thread.
69 	 */
70 	private void waitForData()
71 	{
72 		while(this.m_connected)
73 		{
74 			auto packet = Packet(PACKET_SIZE);
75 
76 			immutable length = this.m_socket.receive(packet.data[]);
77 			
78 			if (length > 0)
79 			{
80 				packet.data.length = length;
81 				this.m_onDataEvent(packet);
82 			}
83 			else 
84 			{
85 				this.m_connected = false;
86 
87 				if (this.m_onDisconnectedEvent !is null)
88 				{
89 					this.m_onDisconnectedEvent();
90 				}
91 			}
92 		}
93 	}
94 	
95 	/**
96 	 * Sends data as struct messages.
97 	 * Params:
98 	 *		 message : message to send
99 	 */
100 	public void send(T)(in auto ref T message)
101 	{
102 		ubyte[] data = message.pack();
103 
104 		data.insertInPlace(0, GetAttribute!(ubyte, "OpCode", T));
105 
106 		this.m_socket.send(data);
107 	}
108 	
109 	/**
110 	 * Receives data.
111 	 */
112 	public int receive(ubyte[] data)
113 	{
114 		return this.m_socket.receive(data);
115 	}
116 	
117 	/**
118 	 * Closes connection.
119 	 */
120 	public void close()
121 	{
122 		this.m_connected = false;
123 		
124 		this.m_socket.shutdown(SocketShutdown.BOTH);
125 		this.m_socket.close();
126 	}
127 	
128 	@nogc @safe
129 	@property pure nothrow
130 	{
131 		public bool connected() const
132 		{
133 			return this.m_connected;
134 		}
135 		
136 		public Socket socket()
137 		{
138 			return this.m_socket;
139 		}
140 		
141 		public void onDataEvent(OnDataEvent value)
142 		{
143 			this.m_onDataEvent = value;
144 		}
145 		
146 		public void onDisconnectedEvent(OnDisconnectedEvent value)
147 		{
148 			this.m_onDisconnectedEvent = value;
149 		}
150 	}
151 }
152 
153 
154 T getStruct(T)(ubyte[] data)
155 {
156 	return data.unpack!T();
157 }
158 
159 template GetAttribute(T, string name, alias symbol)
160 {
161 	import std.string;
162 
163 	T GetAttribute()
164 	{
165 		enum UDAs = __traits(getAttributes, symbol);
166 
167 		static if (UDAs.length)
168 		{
169 			foreach (i, UDA; UDAs)
170 			{
171 				if (UDA.stringof.startsWith(name))
172 					return UDA.value;
173 			}
174 		}
175 		else static assert(false, "No OpCode attribute for message " ~ symbol.stringof);
176 
177 		return 0;
178 	}
179 }