1 module vadgl.error;
2 
3 import std.traits       : EnumMembers;
4 import std.format       : format;
5 
6 import result;
7 import vadgl.types;
8 
9 @safe @nogc nothrow
10 void copy_to_buffer(string str, scope char[] buffer)
11 {
12     size_t chars_to_copy = 
13         (str.length < buffer.length)
14             ? str.length : buffer.length;
15 
16     buffer[0..chars_to_copy] = str[0..chars_to_copy];
17 }
18 
19 @safe @nogc nothrow
20 private string enum_to_str(T)(T f) if (is(T == enum))
21 {
22     import std.traits   : EnumMembers;
23 
24     final switch(f)
25     {
26         static foreach(member; EnumMembers!T) {
27             case member:
28                 return member.stringof;
29         }
30     }
31 }
32 
33 bool is_empty(scope char[] buffer) => (buffer.length == 0 || buffer[0] == 0);
34 
35 /*
36     NOTE:
37         - This thing allocates for strings
38         - ~~There is no wayt to have nice error messages without allocations~~
39         - I could make this 100% @nogc if I use `vadgl.types.GLFuncEnum` for storing
40         functions and save a buffer of say 4 func enums as a function stack. For
41         The extra error msg I could save it as a char[64]. It would increase stack
42         memory and limit the hints I could add but it would be 100% safe.
43         Though to be honest I'm not sure it would be worth the hassle
44 */
45 // TODO: Use strings for error. It will be eaiser
46 // TODO: use Result!(GLError, void) instead of just GLError
47 // TOOD: Instead of putting all errors in a single enum.
48 /// I should make different enums/struct to represent different kind of errors
49 // and store the actual error into an union or sumType
50 
51 // This is absolutely not `@nogc`. But since this is supposed to run in a single thread
52 // and honestly allocations here shouldn't be a problem. If they are I will worry
53 struct GLError
54 {
55     // ALL here be safe!
56     @safe nothrow:
57 
58     enum Flag : uint
59     {
60         NO_ERROR            = GLInternalError.NO_ERROR,
61         // Custom errors
62         // For glBind<Buffer|VertexArray>
63         INVALID_TARGET,
64         INVALID_BUFFER,
65 
66         // shader errors
67         INVALID_SHADER,
68         INVALID_SHADER_TYPE,
69         SHADER_SOURCE_NOT_SET,
70         SHADER_COMPILATION_ERROR,
71 
72         // program errors
73         INVALID_PROGRAM,
74         SHADER_ALREADY_ATTACHED,
75         PROGRAM_LINK_ERROR,
76         PROGRAM_VALIDATION_ERROR,
77 
78         INVALID_PARAMETER,
79 
80         // 1 to 1 mappings to general OpenGL errros
81         INVALID_ENUM        = GLInternalError.INVALID_ENUM,
82         INVALID_VALUE       = GLInternalError.INVALID_VALUE,
83         INVALID_OPERATION   = GLInternalError.INVALID_OPERATION,
84 
85         STACK_OVERFLOW      = GLInternalError.STACK_OVERFLOW,
86         STACK_UNDERFLOW     = GLInternalError.STACK_UNDERFLOW,
87 
88         UNKNOWN_ERROR = int.max
89     }
90 
91     mixin injectEnum!Flag;
92 
93     /*
94         TODO: Perhaps use my own static string type to avoid allocations
95     */
96     Flag error_flag = Flag.NO_ERROR;
97     string fnc = "unknown_error";
98     string msg = "";
99 
100     @nogc pure
101     this(Flag error_flag, string msg="", string func_name=__FUNCTION__)
102     {
103         this.error_flag = error_flag;
104         this.msg = msg;
105         this.fnc = func_name;
106     }
107 
108     @nogc pure
109     this(string func, Flag error_flag = Flag.UNKNOWN_ERROR, string msg = "")
110     {
111         this.error_flag = error_flag;
112         this.fnc = func;
113         this.msg = msg;
114     }
115 
116     /* GLError with_msg(string msg) => GLError(this.error_flag, msg); */
117 
118     // No sides effect here!
119     pure:
120 
121     GLError append(const GLError error) const
122         => GLError(this.fnc, this.error_flag, error.to_error_msg());
123 
124     GLError append(string msg) const
125         => GLError(this.fnc, this.error_flag, this.msg ~ "\n" ~ msg);
126 
127     GLError append_fnc(string fnc_name = __FUNCTION__) const pure
128         => GLError(this.fnc, this.error_flag, this.msg ~ "\n" ~ "\tfrom " ~ fnc_name);
129 
130     string default_msg() const
131         => "[GL_ERROR]: "~this.fnc~": "~error_flag.enum_to_str();
132 
133     // TODO: maybe use toString instead
134     string to_error_msg() const => this.default_msg() ~ this.msg ~ "\n";
135 
136     @nogc
137     bool is_error() const => (this.error_flag != Flag.NO_ERROR);
138 
139     @nogc
140     bool opCast(T: bool)() const => this.is_error();
141 
142     @nogc
143     static GLError no_error() => GLError.NO_ERROR;
144 }
145 
146 // TODO: Make an enum containing all Opengl functions
147 struct InternalError
148 {
149     // All here be safe!
150     @safe nothrow pure:
151 
152     mixin injectEnum!GLInternalError;
153 
154     GLInternalError error_flag;
155     GLFuncEnum fnc = GLFuncEnum.UNKNOWN_GL_FUNCTION;
156 
157     this(GLInternalError error_flag) @nogc
158     {
159         this.error_flag = error_flag;
160     }
161 
162     this(GLFuncEnum fnc, GLInternalError error_flag) @nogc
163     {
164         this.error_flag = error_flag;
165         this.fnc = fnc;
166     }
167 
168     string to_error_msg() const
169         => "[GL_ERROR]: "~cast(string)this.fnc~": "~error_flag.enum_to_str();
170 
171     @nogc
172     bool is_error() const => (this.error_flag != GLInternalError.NO_ERROR);
173 
174     @nogc
175     bool opCast(T: bool)() const => this.is_error();
176     // Technically pure since it always returns the same
177     @nogc nothrow
178     static InternalError no_error() => InternalError.NO_ERROR;
179 }
180 
181 // All here is also safe, does not allocate and cannot throw!
182 @safe @nogc nothrow pure:
183 
184 public alias GLResult = ResultPartial!GLError;
185 
186 // ditto
187 // TODO: auto ref prob doesn't make sense here. Just `T` is better
188 GLResult!T glresult(T)(auto ref T val) if (!is(T == GLError)) => GLResult!T(val);
189 // TODO: put this inside `result.d` and check if Error is a proper
190 // error type
191 GLResult!T glresult(T = void)(GLError err) if (!is(T == GLError)) => GLResult!T(err);
192 
193 // ditto
194 GLError.Flag to_glerror_flag(GLInternalError internal) pure
195 {
196     final switch (internal)
197     {
198         static foreach (flag; EnumMembers!GLInternalError)
199             mixin(q{
200                 case GLInternalError.%1$s:
201                     return GLError.Flag.%1$s;
202             }.format(flag.stringof));
203     }
204 }
205 
206 GLError to_glerror(GLInternalError internal) => GLError(internal.to_glerror_flag());
207 
208 GLError to_glerror(InternalError internal)
209     => GLError(cast(string)internal.fnc, internal.error_flag.to_glerror_flag());
210 
211 GLError.Flag to_glerror_flag(InternalError internal) => internal.to_glerror().error_flag;
212 
213 GLResult!T to_glresult(T)(Result!(InternalError, T) result) if (!is(T == void))
214             => GLResult!T(result.error.to_glerror(), result.value);
215 
216 GLResult!T to_glresult(T)(Result!(InternalError, T) result) if (is(T == void))
217             => GLResult!T(result.error.to_glerror());