Browse Source

added libuv support, tcp only for now

Nicolas Cannasse 8 years ago
parent
commit
4cd420f73d
5 changed files with 386 additions and 22 deletions
  1. 18 1
      hl.sln
  2. 198 15
      libs/uv/uv.c
  3. 6 6
      libs/uv/uv.vcxproj
  4. 111 0
      other/uvsample/UVSample.hx
  5. 53 0
      other/uvsample/uvsample.hxproj

+ 18 - 1
hl.sln

@@ -1,7 +1,7 @@
 
 Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 14
-VisualStudioVersion = 14.0.25123.0
+VisualStudioVersion = 14.0.24720.0
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sdl", "libs\sdl\sdl.vcxproj", "{12049F27-EA26-4A33-ADF8-E542C4167C00}"
 	ProjectSection(ProjectDependencies) = postProject
@@ -17,10 +17,12 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fmt", "libs\fmt\fmt.vcxproj
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hl", "hl.vcxproj", "{BBF750D2-6DD2-4A41-AC3A-07C070B94FA1}"
 	ProjectSection(ProjectDependencies) = postProject
+		{76E4DB00-8114-4B96-BA76-39D800F8D5EE} = {76E4DB00-8114-4B96-BA76-39D800F8D5EE}
 		{7DDA1414-6675-45C7-8254-42057901F865} = {7DDA1414-6675-45C7-8254-42057901F865}
 		{6534D221-34DF-404A-AFCD-6DEC9BBC9798} = {6534D221-34DF-404A-AFCD-6DEC9BBC9798}
 		{12049F27-EA26-4A33-ADF8-E542C4167C00} = {12049F27-EA26-4A33-ADF8-E542C4167C00}
 		{C6213FBF-BC2B-4235-A827-84A60E848C52} = {C6213FBF-BC2B-4235-A827-84A60E848C52}
+		{F4D939D6-88D6-4FF2-874A-7BECF75A01C2} = {F4D939D6-88D6-4FF2-874A-7BECF75A01C2}
 	EndProjectSection
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ui", "libs\ui\ui.vcxproj", "{6534D221-34DF-404A-AFCD-6DEC9BBC9798}"
@@ -34,6 +36,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "libs", "libs", "{0EC4330B-6
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ssl", "libs\ssl\ssl.vcxproj", "{F4D939D6-88D6-4FF2-874A-7BECF75A01C2}"
 EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "uv", "libs\uv\uv.vcxproj", "{76E4DB00-8114-4B96-BA76-39D800F8D5EE}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Win32 = Debug|Win32
@@ -128,6 +132,18 @@ Global
 		{F4D939D6-88D6-4FF2-874A-7BECF75A01C2}.ReleaseVS2013|Win32.Build.0 = ReleaseVS2013|Win32
 		{F4D939D6-88D6-4FF2-874A-7BECF75A01C2}.ReleaseVS2013|x64.ActiveCfg = ReleaseVS2013|x64
 		{F4D939D6-88D6-4FF2-874A-7BECF75A01C2}.ReleaseVS2013|x64.Build.0 = ReleaseVS2013|x64
+		{76E4DB00-8114-4B96-BA76-39D800F8D5EE}.Debug|Win32.ActiveCfg = Debug|Win32
+		{76E4DB00-8114-4B96-BA76-39D800F8D5EE}.Debug|Win32.Build.0 = Debug|Win32
+		{76E4DB00-8114-4B96-BA76-39D800F8D5EE}.Debug|x64.ActiveCfg = Debug|x64
+		{76E4DB00-8114-4B96-BA76-39D800F8D5EE}.Debug|x64.Build.0 = Debug|x64
+		{76E4DB00-8114-4B96-BA76-39D800F8D5EE}.Release|Win32.ActiveCfg = Release|Win32
+		{76E4DB00-8114-4B96-BA76-39D800F8D5EE}.Release|Win32.Build.0 = Release|Win32
+		{76E4DB00-8114-4B96-BA76-39D800F8D5EE}.Release|x64.ActiveCfg = Release|x64
+		{76E4DB00-8114-4B96-BA76-39D800F8D5EE}.Release|x64.Build.0 = Release|x64
+		{76E4DB00-8114-4B96-BA76-39D800F8D5EE}.ReleaseVS2013|Win32.ActiveCfg = ReleaseVS2013|Win32
+		{76E4DB00-8114-4B96-BA76-39D800F8D5EE}.ReleaseVS2013|Win32.Build.0 = ReleaseVS2013|Win32
+		{76E4DB00-8114-4B96-BA76-39D800F8D5EE}.ReleaseVS2013|x64.ActiveCfg = ReleaseVS2013|x64
+		{76E4DB00-8114-4B96-BA76-39D800F8D5EE}.ReleaseVS2013|x64.Build.0 = ReleaseVS2013|x64
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -138,5 +154,6 @@ Global
 		{6534D221-34DF-404A-AFCD-6DEC9BBC9798} = {0EC4330B-6B61-45F8-B297-CA7097AFFD98}
 		{43AAD9DB-4708-46B6-AAE8-A5DB95A0B573} = {0EC4330B-6B61-45F8-B297-CA7097AFFD98}
 		{F4D939D6-88D6-4FF2-874A-7BECF75A01C2} = {0EC4330B-6B61-45F8-B297-CA7097AFFD98}
+		{76E4DB00-8114-4B96-BA76-39D800F8D5EE} = {0EC4330B-6B61-45F8-B297-CA7097AFFD98}
 	EndGlobalSection
 EndGlobal

+ 198 - 15
libs/uv/uv.c

@@ -2,31 +2,214 @@
 #include <uv.h>
 #include <hl.h>
 
-// loop
+#define EVT_CLOSE	1
 
-#define _LOOP _ABSTRACT(uv_loop)
-DEFINE_PRIM(_LOOP, default_loop, _NO_ARG);
-DEFINE_PRIM(_I32, loop_close, _LOOP);
-DEFINE_PRIM(_I32, run, _LOOP _I32);
-DEFINE_PRIM(_I32, loop_alive, _LOOP);
-DEFINE_PRIM(_VOID, stop, _LOOP);
+#define EVT_READ	0	// stream
+#define EVT_LISTEN	2	// stream
 
-DEFINE_PRIM(_BYTES, strerror, _I32);
+#define EVT_WRITE	0	// write_t
+#define EVT_CONNECT	0	// connect_t
 
-// HANDLE
+#define EVT_MAX		2
+
+typedef struct {
+	vclosure *events[EVT_MAX + 1];
+	uv_buf_t buf;
+} events_data;
 
+#define UV_DATA(h)		((events_data*)((h)->data))
+
+#define _LOOP	_ABSTRACT(uv_loop)
 #define _HANDLE _ABSTRACT(uv_handle)
-//DEFINE_PRIM(_VOID, close, _HANDLE);
+#define _CALLB	_FUN(_VOID,_NO_ARG)
+#define UV_ALLOC(t)		((t*)malloc(sizeof(t)))
+
+// HANDLE
+
+static events_data *init_hl_data( uv_handle_t *h ) {
+	events_data *d = hl_gc_alloc_raw(sizeof(events_data));
+	memset(d,0,sizeof(events_data));
+	hl_add_root(&h->data);
+	h->data = d;
+	return d;
+}
+
+static void register_callb( uv_handle_t *h, vclosure *c, int event_kind ) {
+	if( !h || !h->data ) return;
+	UV_DATA(h)->events[event_kind] = c;
+}
+
+static void clear_callb( uv_handle_t *h, int event_kind ) {
+	register_callb(h,NULL,event_kind);
+}
+
+static void trigger_callb( uv_handle_t *h, int event_kind, vdynamic **args, int nargs, bool repeat ) {
+	events_data *ev = UV_DATA(h);
+	vclosure *c = ev ? ev->events[event_kind] : NULL;
+	if( !c ) return;
+	if( !repeat ) ev->events[event_kind] = NULL;
+	hl_dyn_call(c, args, nargs);
+}
+
+static void on_close( uv_handle_t *h ) {
+	trigger_callb(h, EVT_CLOSE, NULL, 0, false);
+	hl_remove_root(&h->data);
+	h->data = NULL;
+	free(h);
+}
+
+static void free_handle( void *h ) {
+	if( h ) uv_close((uv_handle_t*)h, on_close);
+}
+
+HL_PRIM void HL_NAME(close_handle)( uv_handle_t *h, vclosure *c ) {	
+	register_callb(h, c, EVT_CLOSE);
+	free_handle(h);
+}
+
+DEFINE_PRIM(_VOID, close_handle, _HANDLE _CALLB);
+
+// STREAM
+
+static void on_write( uv_write_t *wr, int status ) {
+	vdynamic b;
+	vdynamic *args = &b;
+	b.t = &hlt_bool;
+	b.v.b = status == 0;
+	trigger_callb((uv_handle_t*)wr,EVT_WRITE,&args,1,false);
+	on_close((uv_handle_t*)wr);
+}
+
+HL_PRIM bool HL_NAME(stream_write)( uv_stream_t *s, vbyte *b, int size, vclosure *c ) {
+	uv_write_t *wr = UV_ALLOC(uv_write_t);
+	events_data *d = init_hl_data((uv_handle_t*)wr);
+	d->buf.base = b;
+	d->buf.len = size;
+	register_callb((uv_handle_t*)wr,c,EVT_WRITE);
+	if( uv_write(wr,s,&d->buf,1,on_write) < 0 ) {
+		on_close((uv_handle_t*)wr);
+		return false;
+	}
+	return true;
+}
+
+static void on_alloc( uv_handle_t* h, size_t size, uv_buf_t *buf ) {
+	*buf = uv_buf_init(malloc(size), size);
+} 
+
+static void on_read( uv_stream_t *s, ssize_t nread, const uv_buf_t *buf ) {
+	vdynamic bytes;
+	vdynamic len;
+	vdynamic *args[2];
+	bytes.t = &hlt_bytes;
+	bytes.v.ptr = buf->base;
+	len.t = &hlt_i32;
+	len.v.i = nread;
+	args[0] = &bytes;
+	args[1] = &len;
+	trigger_callb((uv_handle_t*)s,EVT_READ,args,2,true);
+	free(buf->base);
+}
+
+HL_PRIM bool HL_NAME(stream_read_start)( uv_stream_t *s, vclosure *c ) {
+	register_callb((uv_handle_t*)s,c,EVT_READ);
+	return uv_read_start(s,on_alloc,on_read) >= 0;
+}
+
+HL_PRIM void HL_NAME(stream_read_stop)( uv_stream_t *s ) {
+	uv_read_stop(s);
+	clear_callb((uv_handle_t*)s,EVT_READ); // clear callback	
+}
+
+static void on_listen( uv_stream_t *s, int status ) {
+	trigger_callb((uv_handle_t*)s, EVT_LISTEN, NULL, 0, true);
+}
+
+HL_PRIM bool HL_NAME(stream_listen)( uv_stream_t *s, int count, vclosure *c ) {
+	register_callb((uv_handle_t*)s,c,EVT_LISTEN);
+	return uv_listen(s,count,on_listen) >= 0;
+}
+
+DEFINE_PRIM(_BOOL, stream_write, _HANDLE _BYTES _I32 _FUN(_VOID,_BOOL));
+DEFINE_PRIM(_BOOL, stream_read_start, _HANDLE _FUN(_VOID,_BYTES _I32));
+DEFINE_PRIM(_VOID, stream_read_stop, _HANDLE);
+DEFINE_PRIM(_BOOL, stream_listen, _HANDLE _I32 _CALLB);
 
 // TCP
 
-#define _TCP _ABSTRACT(uv_tcp)
+#define _TCP _HANDLE
 
-HL_PRIM uv_tcp_t *uv_tcp_new( uv_loop_t *loop ) {
-	uv_tcp_t *t = (uv_tcp_t*)malloc(sizeof(uv_tcp_t));
-	if( uv_tcp_init(loop,t) < 0 )
+HL_PRIM uv_tcp_t *HL_NAME(tcp_init_wrap)( uv_loop_t *loop ) {
+	uv_tcp_t *t = UV_ALLOC(uv_tcp_t);
+	if( uv_tcp_init(loop,t) < 0 ) {
+		free(t);
 		return NULL;
+	}
+	init_hl_data((uv_handle_t*)t);
 	return t;
 }
 
-DEFINE_PRIM(_TCP, tcp_new, _LOOP);
+static void on_connect( uv_connect_t *cnx, int status ) {
+	vdynamic b;
+	vdynamic *args = &b;
+	b.t = &hlt_bool;
+	b.v.b = status == 0;
+	trigger_callb((uv_handle_t*)cnx,EVT_CONNECT,&args,1,false);
+	on_close((uv_handle_t*)cnx);
+}
+
+HL_PRIM uv_connect_t *HL_NAME(tcp_connect_wrap)( uv_tcp_t *t, int host, int port, vclosure *c ) {
+	uv_connect_t *cnx = UV_ALLOC(uv_connect_t);
+	struct sockaddr_in addr;
+	memset(&addr,0,sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons((unsigned short)port);
+	*(int*)&addr.sin_addr.s_addr = host;
+	if( !t || uv_tcp_connect(cnx,t,(struct sockaddr *)&addr,on_connect) < 0 ) {
+		free(cnx);
+		return NULL;
+	}
+	memset(&addr,0,sizeof(addr));
+	init_hl_data((uv_handle_t*)cnx);
+	register_callb((uv_handle_t*)cnx, c, EVT_CONNECT);
+	return cnx;
+}
+
+HL_PRIM bool HL_NAME(tcp_bind_wrap)( uv_tcp_t *t, int host, int port ) {
+	struct sockaddr_in addr;
+	memset(&addr,0,sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons((unsigned short)port);
+	*(int*)&addr.sin_addr.s_addr = host;
+	return uv_tcp_bind(t,(struct sockaddr *)&addr,0) >= 0;
+}
+
+
+HL_PRIM uv_tcp_t *HL_NAME(tcp_accept)( uv_tcp_t *t ) {
+	uv_tcp_t *client = UV_ALLOC(uv_tcp_t);
+	if( uv_tcp_init(t->loop, client) < 0 ) {
+		free(client);
+		return NULL;
+	}
+	if( uv_accept((uv_stream_t*)t,(uv_stream_t*)client) < 0 ) {
+		uv_close((uv_handle_t*)client, NULL);
+		return NULL;
+	}
+	init_hl_data((uv_handle_t*)client);
+	return client;
+}
+
+DEFINE_PRIM(_TCP, tcp_init_wrap, _LOOP);
+DEFINE_PRIM(_HANDLE, tcp_connect_wrap, _TCP _I32 _I32 _FUN(_VOID,_BOOL));
+DEFINE_PRIM(_BOOL, tcp_bind_wrap, _TCP _I32 _I32);
+DEFINE_PRIM(_HANDLE, tcp_accept, _HANDLE);
+
+// loop
+
+DEFINE_PRIM(_LOOP, default_loop, _NO_ARG);
+DEFINE_PRIM(_I32, loop_close, _LOOP);
+DEFINE_PRIM(_I32, run, _LOOP _I32);
+DEFINE_PRIM(_I32, loop_alive, _LOOP);
+DEFINE_PRIM(_VOID, stop, _LOOP);
+
+DEFINE_PRIM(_BYTES, strerror, _I32);

+ 6 - 6
libs/uv/uv.vcxproj

@@ -145,7 +145,7 @@
       <SubSystem>Windows</SubSystem>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <OutputFile>../../$(Configuration)/$(TargetName).hdll</OutputFile>
-      <AdditionalDependencies>libhl.lib;libuv.lib</AdditionalDependencies>
+      <AdditionalDependencies>libhl.lib;libuv.lib;ws2_32.lib</AdditionalDependencies>
     </Link>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@@ -160,7 +160,7 @@
       <SubSystem>Windows</SubSystem>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
-      <AdditionalDependencies>libhl.lib;libuv.lib</AdditionalDependencies>
+      <AdditionalDependencies>libhl.lib;libuv.lib;ws2_32.lib</AdditionalDependencies>
     </Link>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@@ -180,7 +180,7 @@
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
       <OutputFile>../../$(Configuration)/$(TargetName).hdll</OutputFile>
-      <AdditionalDependencies>libhl.lib;libuv.lib</AdditionalDependencies>
+      <AdditionalDependencies>libhl.lib;libuv.lib;ws2_32.lib</AdditionalDependencies>
     </Link>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseVS2013|Win32'">
@@ -200,7 +200,7 @@
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
       <OutputFile>../../$(Configuration)/$(TargetName).hdll</OutputFile>
-      <AdditionalDependencies>libhl.lib;libuv.lib</AdditionalDependencies>
+      <AdditionalDependencies>libhl.lib;libuv.lib;ws2_32.lib</AdditionalDependencies>
     </Link>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@@ -218,7 +218,7 @@
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
       <OptimizeReferences>true</OptimizeReferences>
       <GenerateDebugInformation>true</GenerateDebugInformation>
-      <AdditionalDependencies>libhl.lib;libuv.lib</AdditionalDependencies>
+      <AdditionalDependencies>libhl.lib;libuv.lib;ws2_32.lib</AdditionalDependencies>
     </Link>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseVS2013|x64'">
@@ -236,7 +236,7 @@
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
       <OptimizeReferences>true</OptimizeReferences>
       <GenerateDebugInformation>true</GenerateDebugInformation>
-      <AdditionalDependencies>libhl.lib;libuv.lib</AdditionalDependencies>
+      <AdditionalDependencies>libhl.lib;libuv.lib;ws2_32.lib</AdditionalDependencies>
     </Link>
   </ItemDefinitionGroup>
   <ItemGroup>

+ 111 - 0
other/uvsample/UVSample.hx

@@ -0,0 +1,111 @@
+import hl.uv.*;
+
+class UVSample {
+
+	static var T0 = haxe.Timer.stamp();
+
+	static function log( msg : String ) {
+		Sys.println("["+Std.int((haxe.Timer.stamp() - T0) * 100)+"] "+msg);
+	}
+
+	static function main() {
+		var loop = Loop.getDefault();
+		var tcp = new Tcp(loop);
+
+		/*
+		tcp.connect(new sys.net.Host("google.com"), 80, function(b) {
+
+			log("Connected=" + b);
+			var bytes = haxe.io.Bytes.ofString("GET / HTTP/1.0\r\nHost: www.google.com\r\n\r\n");
+			tcp.write(bytes, function(b) trace("Sent=" + b));
+			var buf = new haxe.io.BytesBuffer();
+			tcp.readStart(function(bytes:hl.Bytes, len) {
+				if( len < 0 ) {
+					var str = buf.getBytes().toString();
+					if( str.length > 1000 )
+						str = str.substr(0, 500) + "\n...\n" + str.substr(str.length - 500);
+					log("#" + str + "#");
+					tcp.readStop();
+					return;
+				}
+				log("Read="+len);
+				var bytes = bytes.toBytes(len);
+				buf.addBytes(bytes, 0, len);
+			});
+
+		});
+		*/
+
+		var host = new sys.net.Host("localhost");
+		var port = 6001;
+
+		var totR = 0, totW = 0, totRB = 0;
+
+		log("Starting server");
+		tcp.bind(host, port);
+		tcp.listen(5, function() {
+
+			log("Client connected");
+			var s = tcp.accept();
+			s.readStart(function(bytes) {
+				totR += bytes.length;
+				// write back
+				s.write(bytes, function(b) if( !b ) throw "Write failure");
+			});
+
+		});
+
+
+		function startClient() {
+
+			var numbers = [];
+			var client = new Tcp(loop);
+			log("Connecting...");
+			client.connect(host, port, function(b) {
+				log("Connected to server");
+
+
+				function send() {
+					var b = haxe.io.Bytes.alloc(1);
+					var k = Std.random(255);
+					numbers.push(k);
+					totW++;
+					b.set(0, k);
+					client.write(b, function(b) if( !b ) log("Write failure"));
+				}
+
+				function sendBatch() {
+					for( i in 0...1+Std.random(10) )
+						send();
+				}
+				sendBatch();
+
+				client.readStart(function(b) {
+					totRB += b.length;
+					for( i in 0...b.length ) {
+						var k = b.get(i);
+						if( !numbers.remove(k) )
+							throw "!";
+					}
+					if( numbers.length == 0 )
+						sendBatch();
+				});
+
+			});
+
+		}
+
+		for( i in 0...4 )
+			startClient();
+
+
+		log("Enter Loop");
+
+		var K = 0;
+		while( loop.run(NoWait) != 0 )
+			if( K++ % 10000 == 0 ) log("Read=" + totR);
+
+		log("Done");
+	}
+
+}

+ 53 - 0
other/uvsample/uvsample.hxproj

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<project version="2">
+  <!-- Output SWF options -->
+  <output>
+    <movie outputType="CustomBuild" />
+    <movie input="" />
+    <movie path="" />
+    <movie fps="0" />
+    <movie width="0" />
+    <movie height="0" />
+    <movie version="0" />
+    <movie minorVersion="0" />
+    <movie platform="JavaScript" />
+    <movie background="#FFFFFF" />
+  </output>
+  <!-- Other classes to be compiled into your SWF -->
+  <classpaths>
+    <!-- example: <class path="..." /> -->
+  </classpaths>
+  <!-- Build options -->
+  <build>
+    <option directives="" />
+    <option flashStrict="False" />
+    <option noInlineOnDebug="False" />
+    <option mainClass="" />
+    <option enabledebug="False" />
+    <option additional="" />
+  </build>
+  <!-- haxelib libraries -->
+  <haxelib>
+    <!-- example: <library name="..." /> -->
+  </haxelib>
+  <!-- Class files to compile (other referenced classes will automatically be included) -->
+  <compileTargets>
+    <!-- example: <compile path="..." /> -->
+  </compileTargets>
+  <!-- Paths to exclude from the Project Explorer tree -->
+  <hiddenPaths>
+    <hidden path="obj" />
+  </hiddenPaths>
+  <!-- Executed before build -->
+  <preBuildCommand>haxe -hl uvsample.hl -main UVSample -dce no</preBuildCommand>
+  <!-- Executed after build -->
+  <postBuildCommand alwaysRun="False" />
+  <!-- Other project options -->
+  <options>
+    <option showHiddenPaths="False" />
+    <option testMovie="Custom" />
+    <option testMovieCommand="run.bat" />
+  </options>
+  <!-- Plugin storage -->
+  <storage />
+</project>