1 module gfm.enet.enet;
2 
3 import derelict.enet.enet;
4 import derelict.util.exception;
5 
6 import std.experimental.logger;
7 
8 /// General ENet exception thrown for all cases.
9 final class ENetException : Exception
10 {
11     @safe pure nothrow this(string message, string file =__FILE__, size_t line = __LINE__, Throwable next = null)
12     {
13         super(message, file, line, next);
14     }
15 }
16 
17 /// Owns the loader, logging, keyboard state...
18 /// This object is passed around to other ENet wrapper objects
19 /// to ensure library loading.
20 final class ENet
21 {
22     public
23     {
24         /// Loads DerelictENet and initializes the ENet library.
25         /// Throws: ENetException when enet_initialize fails.
26         this(Logger logger = null)
27         {
28             _logger = logger is null ? new NullLogger() : logger;
29 
30             ShouldThrow missingSymFunc( string symName )
31             {
32                 // Supports from libenet 1.3.3 to 1.3.11+
33                 // Obviously we should take extras care in gfm:enet
34                 // not to strictly rely on these functions.
35 
36                 if (symName == "enet_linked_version")
37                     return ShouldThrow.No;
38 
39                 if (symName == "enet_socket_get_address")
40                     return ShouldThrow.No;
41 
42                 if (symName == "enet_socket_get_option")
43                     return ShouldThrow.No;
44 
45                 if (symName == "enet_socket_shutdown")
46                     return ShouldThrow.No;
47 
48                 if (symName == "enet_host_random_seed")
49                     return ShouldThrow.No;
50 
51                 if (symName == "enet_peer_ping_interval")
52                     return ShouldThrow.No;
53 
54                 if (symName == "enet_peer_timeout")
55                     return ShouldThrow.No;
56 
57                 if (symName == "enet_peer_on_connect")
58                     return ShouldThrow.No;
59 
60                 if (symName == "enet_peer_on_disconnect")
61                     return ShouldThrow.No;
62 
63                 // Any other missing symbol should throw.
64                 return ShouldThrow.Yes;
65             }
66 
67             DerelictENet.missingSymbolCallback = &missingSymFunc;
68 
69             try
70                 DerelictENet.load();
71             catch(DerelictException e)
72                 throw new ENetException(e.msg);
73 
74             int errCode = enet_initialize();
75             if(errCode < 0)
76                 throw new ENetException("enet_initialize failed");
77 
78             _enetInitialized = true;
79         }
80 
81         /// Deinitializes the ENet library and unloads DerelictENet.
82         ~this()
83         {
84             if(_enetInitialized)
85             {
86                 debug ensureNotInGC("ENet");
87                 enet_deinitialize();
88                 _enetInitialized = false;
89             }
90         }
91     }
92 
93     package
94     {
95         Logger _logger;
96     }
97 
98     private
99     {
100         bool _enetInitialized = false;
101     }
102 }
103 
104 /// Crash if the GC is running.
105 /// Useful in destructors to avoid reliance GC resource release.
106 package void ensureNotInGC(string resourceName) nothrow
107 {
108     import core.exception;
109     try
110     {
111         import core.memory;
112         cast(void) GC.malloc(1); // not ideal since it allocates
113         return;
114     }
115     catch(InvalidMemoryOperationError e)
116     {
117 
118         import core.stdc.stdio;
119         fprintf(stderr, "Error: clean-up of %s incorrectly depends on destructors called by the GC.\n", resourceName.ptr);
120         assert(false);
121     }
122 }