private static void WriteProtocol(XContainer doc, CSharpWriter w) { // for writing the high level API var hlw = new CSharpWriter(); var protocolElement = doc.Element(ProtocolElement); if (protocolElement == null) { throw new Exception("No protocol element found!"); } var protocolName = protocolElement.Attribute(NameAttrib).Value; w.Line($"// Protocol: {protocolName}"); w.Line(); w.Line("using System;"); w.Line("using System.Runtime.InteropServices;"); w.Line(); hlw.Line("namespace OpenWindow.Backends.Wayland.Managed"); hlw.OpenBlock(); w.Line("namespace OpenWindow.Backends.Wayland"); w.OpenBlock(); var interfaceElements = protocolElement.Elements(InterfaceElement); var ifaces = interfaceElements.Select(e => new Interface(e)).ToArray(); var messages = ifaces.SelectMany(iface => iface.Requests.Concat(iface.Events)).ToArray(); var ifaceCount = ifaces.Length; var msgCount = messages.Length; _protocolClassName = Util.ToPascalCase(protocolName) + "Bindings"; w.Line($"internal static unsafe partial class {_protocolClassName}"); w.OpenBlock(); w.Line("private static bool _loaded;"); w.Line(); w.Line("public static wl_interface* Interfaces;"); w.Line("private static wl_message* _messages;"); w.Line("private static wl_interface** _signatureTypes;"); w.Line(); w.Line($"private static readonly int InterfaceCount = {ifaceCount};"); w.Line($"private static readonly int MessageCount = {msgCount};"); w.Line(); foreach (var iface in ifaces) { w.Line($"public const string {iface.RawName}_name = \"{iface.RawName}\";"); } w.Line(); w.Line("public static void Load()"); w.OpenBlock(); w.Line("if (_loaded)"); w.LineIndented("return;"); w.Line("_loaded = true;"); w.Line(); w.Line("Interfaces = (wl_interface*) Marshal.AllocHGlobal(sizeof(wl_interface) * InterfaceCount);"); w.Line("_messages = (wl_message*) Marshal.AllocHGlobal(sizeof(wl_message) * MessageCount);"); w.Line(); w.Line(); for (var i = 0; i < ifaces.Length; i++) { var iface = ifaces[i]; w.Line($"Util.CreateInterface(&Interfaces[{i}], \"{iface.RawName}\", {iface.Version}, {iface.Requests.Length}, {iface.Events.Length});"); } w.Line(); var signatureTypes = new List <string>(); var typeMap = new List <(string TypeStr, int Index)>(); var typeMapIndex = 0; // handle messages with the most types first so we don't create duplicates foreach (var message in messages.OrderByDescending(m => m.SigTypes.Length)) { var typesIndex = 0; var typeStr = ';' + string.Join(";", message.SigTypes) + ';'; var match = typeMap.Where(t => t.TypeStr.Contains(typeStr)); if (match.Any()) { var m = match.First(); var offset = m.TypeStr.Take(m.TypeStr.IndexOf(typeStr)).Count(c => c == ';'); typeMap.Add((typeStr, m.Index + offset)); typesIndex = m.Index + offset; } else { typeMap.Add((typeStr, typeMapIndex)); typesIndex = typeMapIndex; foreach (var type in message.SigTypes) { var sigTypeStr = type == string.Empty ? null : type; signatureTypes.Add(sigTypeStr); typeMapIndex++; } } message.SigTypesIndex = typesIndex; } w.Line($"_signatureTypes = (wl_interface**) Marshal.AllocHGlobal(sizeof(void*) * {signatureTypes.Count});"); for (int i = 0; i < signatureTypes.Count; i++) { var stype = signatureTypes[i]; var stypeStr = stype == null ? "null" : stype + ".Interface"; w.Line($"_signatureTypes[{i}] = {stypeStr};"); } w.Line(); for (var i = 0; i < messages.Length; i++) { var message = messages[i]; w.Line($"Util.CreateMessage(&_messages[{i}], \"{message.RawName}\", \"{message.Signature}\", &_signatureTypes[{message.SigTypesIndex}]);"); } w.Line(); var msgIndex = 0; for (var i = 0; i < ifaces.Length; i++) { var iface = ifaces[i]; var reqInit = iface.Requests.Length == 0 ? "null" : $"&_messages[{msgIndex}]"; w.Line($"Interfaces[{i}].Requests = {reqInit};"); msgIndex += iface.Requests.Length; var evInit = iface.Events.Length == 0 ? "null" : $"&_messages[{msgIndex}]"; w.Line($"Interfaces[{i}].Events = {evInit};"); msgIndex += iface.Events.Length; } w.CloseBlock(); w.Line(); w.Line("public static void Unload()"); w.OpenBlock(); w.Line("if (!_loaded)"); w.LineIndented("return;"); w.Line("_loaded = false;"); w.Line(); w.Line("for (var i = 0; i < InterfaceCount; i++)"); w.LineIndented($"Marshal.FreeHGlobal((IntPtr) Interfaces[i].Name);"); w.Line(); w.Line("for (var i = 0; i < MessageCount; i++)"); w.OpenBlock(); w.Line("Marshal.FreeHGlobal((IntPtr) _messages[i].Name);"); w.Line("Marshal.FreeHGlobal((IntPtr) _messages[i].Signature);"); w.CloseBlock(); w.Line(); w.Line("Marshal.FreeHGlobal((IntPtr) _messages);"); w.Line("Marshal.FreeHGlobal((IntPtr) _signatureTypes);"); w.Line("Marshal.FreeHGlobal((IntPtr) Interfaces);"); w.CloseBlock(); w.Line(); foreach (var iface in ifaces) { hlw.Line($"internal unsafe partial struct {iface.ClsName}"); hlw.OpenBlock(); hlw.Line($"public static IntPtr Interface => (IntPtr) {iface.RawName}.Interface;"); hlw.Line($"public static {iface.ClsName} Null => new {iface.ClsName}();"); hlw.Line($"public readonly {iface.RawName}* Pointer;"); hlw.Line("public bool IsNull => Pointer == null;"); if (iface.Events.Length != 0) { // cached delegates to prevent GC foreach (var ev in iface.Events) { hlw.Line($"private {_protocolClassName}.{GetDelegateName(iface, ev)} _{ev.RawName};"); } // unmanaged listener, needs to be freed by users hlw.Line($"private {_protocolClassName}.{iface.RawName}_listener* _listener;"); } // we need to initialize all fields in the ctor var eventInitializer = iface.Events.Length == 0 ? "" : " _listener = null; " + string.Join(' ', iface.Events.Select(ev => "_" + ev.RawName + " = null;")); hlw.Line($"public {iface.ClsName}({iface.RawName}* ptr) {{ Pointer = ptr;{eventInitializer} }}"); hlw.Line($"public static implicit operator {iface.ClsName}({iface.RawName}* ptr) => new {iface.ClsName}(ptr);"); hlw.Line($"public static explicit operator {iface.ClsName}(wl_proxy* ptr) => new {iface.ClsName}(({iface.RawName}*) ptr);"); WriteRequests(iface, w, hlw); WriteEvents(iface, w, hlw); if (!iface.Requests.Any(m => "destructor".Equals(m.Type))) { hlw.Line("public void Destroy() { if (!IsNull) WaylandClient.wl_proxy_destroy((wl_proxy*) Pointer); }"); } hlw.CloseBlock(); } w.CloseBlock(); // class {ProtocolName}Bindings*/ w.Line(); // dummy structs for typed pointers for (var i = 0; i < ifaces.Length; i++) { var iface = ifaces[i]; WriteDescription(iface.Element, w); w.Line($"internal struct {iface.RawName} {{ public static unsafe wl_interface* Interface => &{_protocolClassName}.Interfaces[{i}]; }}"); } w.Line(); foreach (var iface in ifaces) { foreach (var e in iface.Enums) { w.Line(); WriteEnum(iface, e, w); } } w.CloseBlock(); // namespace hlw.CloseBlock(); // namespace w.Line(); w.Write(hlw); }