mirror of
https://github.com/throwtheswitch/cexception.git
synced 2026-07-22 03:54:15 +00:00
Update to markdown docs. Remove old docs. Grab latest Unity
This commit is contained in:
@@ -1,17 +1,19 @@
|
||||
====================================================================
|
||||
|
||||
CException
|
||||
====================================================================
|
||||
==========
|
||||
|
||||
CException is a basic exception framework for C, suitable for use in
|
||||
embedded applications. It provides an exception framework similar in
|
||||
use to C++, but with much less overhead.
|
||||
|
||||
CException uses C standard library functions setjmp and longjmp to
|
||||
|
||||
CException uses C standard library functions `setjmp` and `longjmp` to
|
||||
operate. As long as the target system has these two functions defined,
|
||||
this library should be useable with very little configuration. It
|
||||
even supports environments where multiple program flows are in use,
|
||||
such as real-time operating systems.
|
||||
|
||||
|
||||
There are about a gabillion exception frameworks using a similar
|
||||
setjmp/longjmp method out there... and there will probably be more
|
||||
in the future. Unfortunately, when we started our last embedded
|
||||
@@ -19,142 +21,149 @@ project, all those that existed either (a) did not support multiple
|
||||
tasks (therefore multiple stacks) or (b) were way more complex than
|
||||
we really wanted. CException was born.
|
||||
|
||||
Why use CException?
|
||||
|
||||
*Why use CException?*
|
||||
|
||||
|
||||
0. It's ANSI C, and it beats passing error codes around.
|
||||
|
||||
1. You want something simple... CException throws a single id. You can
|
||||
define those ID's to be whatever you like. You might even choose which
|
||||
type that number is for your project. But that's as far as it goes.
|
||||
We weren't interested in passing objects or structs or strings...
|
||||
just simple error codes.
|
||||
|
||||
2. Performance... CException can be configured for single tasking or
|
||||
multitasking. In single tasking, there is very little overhead past
|
||||
the setjmp/longjmp calls (which are already fast). In multitasking,
|
||||
your only additional overhead is the time it takes you to determine
|
||||
a unique task id 0 - num_tasks.
|
||||
|
||||
For the latest version, go to http://cexception.sourceforge.net
|
||||
|
||||
--------------------------------------------------------------------
|
||||
For the latest version, go to [ThrowTheSwitch.org](http://throwtheswitch.org)
|
||||
|
||||
|
||||
CONTENTS OF THIS DOCUMENT
|
||||
--------------------------------------------------------------------
|
||||
=========================
|
||||
|
||||
* Usage
|
||||
* Limitations
|
||||
*API
|
||||
* Configuration
|
||||
* Testing
|
||||
* License
|
||||
|
||||
|
||||
Usage
|
||||
Limitations
|
||||
API
|
||||
Configuration
|
||||
Testing
|
||||
License
|
||||
-----
|
||||
|
||||
--------------------------------------------------------------------
|
||||
Usage
|
||||
--------------------------------------------------------------------
|
||||
|
||||
Code that is to be protected are wrapped in Try { } Catch { } blocks.
|
||||
Code that is to be protected are wrapped in `Try { } Catch { }` blocks.
|
||||
The code directly following the Try call is "protected", meaning that
|
||||
if any Throws occur, program control is directly transferred to the
|
||||
start of the Catch block.
|
||||
|
||||
|
||||
A numerical exception ID is included with Throw, and is made accessible
|
||||
from the Catch block.
|
||||
|
||||
|
||||
Throws can occur from within function calls (nested as deeply as you
|
||||
like) or directly from within the function itself.
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
|
||||
Limitations
|
||||
--------------------------------------------------------------------
|
||||
-----------
|
||||
|
||||
|
||||
This library was made to be as fast as possible, and provide basic
|
||||
exception handling. It is not a full-blown exception library. Because
|
||||
of this, there are a few limitations that should be observed in order
|
||||
to successfully utilize this library:
|
||||
|
||||
1. Do not directly "return" from within a Try block, nor "goto"
|
||||
into or out of a Try block.
|
||||
1. Do not directly "return" from within a `Try` block, nor `goto`
|
||||
into or out of a `Try` block.
|
||||
|
||||
Why?
|
||||
*Why?*
|
||||
|
||||
The "Try" macro allocates some local memory and alters a global
|
||||
pointer. These are cleaned up at the top of the "Catch" macro.
|
||||
The `Try` macro allocates some local memory and alters a global
|
||||
pointer. These are cleaned up at the top of the `Catch` macro.
|
||||
Gotos and returns would bypass some of these steps, resulting in
|
||||
memory leaks or unpredictable behavior.
|
||||
|
||||
2. If (a) you change local (stack) variables within your Try block,
|
||||
AND (b) wish to make use of the updated values after an exception
|
||||
is thrown, those variables should be made volatile. Note that this
|
||||
is ONLY for locals and ONLY when you need access to them after a
|
||||
throw.
|
||||
|
||||
Why?
|
||||
2. If (a) you change local (stack) variables within your `Try` block,
|
||||
AND (b) wish to make use of the updated values after an exception
|
||||
is thrown, those variables should be made `volatile`. Note that this
|
||||
is ONLY for locals and ONLY when you need access to them after a
|
||||
`Throw`.
|
||||
|
||||
*Why?*
|
||||
|
||||
Compilers optimize. There is no way to guarantee that the actual
|
||||
memory location was updated and not just a register unless the
|
||||
variable is marked volatile.
|
||||
|
||||
3. Memory which is malloc'd or new'd is not automatically released
|
||||
|
||||
3. Memory which is `malloc`'d or `new`'d is not automatically released
|
||||
when an error is thrown. This will sometimes be desirable, and
|
||||
othertimes may not. It will be the responsibility of the Catch
|
||||
othertimes may not. It will be the responsibility of the `Catch`
|
||||
block to perform this kind of cleanup.
|
||||
|
||||
Why?
|
||||
*Why?*
|
||||
|
||||
There's just no easy way to track malloc'd memory, etc., without
|
||||
There's just no easy way to track `malloc`'d memory, etc., without
|
||||
replacing or wrapping malloc calls or something like that. This
|
||||
is a light framework, so these options were not desirable.
|
||||
|
||||
--------------------------------------------------------------------
|
||||
API
|
||||
--------------------------------------------------------------------
|
||||
|
||||
Try
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
Try is a macro which starts a protected block. It MUST be followed by
|
||||
###Try
|
||||
|
||||
`Try` is a macro which starts a protected block. It MUST be followed by
|
||||
a pair of braces or a single protected line (similar to an 'if'),
|
||||
enclosing the data that is to be protected. It MUST be followed by a
|
||||
Catch block (don't worry, you'll get compiler errors to let you know if
|
||||
enclosing the data that is to be protected. It **must** be followed by a
|
||||
`Catch` block (don't worry, you'll get compiler errors to let you know if
|
||||
you mess any of that up).
|
||||
|
||||
Catch(e)
|
||||
--------
|
||||
|
||||
Catch is a macro which ends the Try block and starts the error handling
|
||||
block. The catch block is called if and only if an exception was thrown
|
||||
while within the Try block. This error was thrown by a Throw call
|
||||
somewhere within Try (or within a function called within Try, or a function
|
||||
called by a function called within Try, etc).
|
||||
###Catch(e)
|
||||
|
||||
The single parameter 'e' is filled with the error code which was thrown.
|
||||
`Catch` is a macro which ends the `Try` block and starts the error handling
|
||||
block. The `Catch` block is called if and only if an exception was thrown
|
||||
while within the `Try` block. This error was thrown by a `Throw` call
|
||||
somewhere within `Try` (or within a function called within `Try`, or a function
|
||||
called by a function called within `Try`, etc).
|
||||
|
||||
The single parameter `e` is filled with the error code which was thrown.
|
||||
This can be used for reporting, conditional cleanup, etc. (or you can just
|
||||
ignore it if you really want... people ignore return codes all the time,
|
||||
right?). 'e' should be of type EXCEPTION_T;
|
||||
right?). `e` should be of type `EXCEPTION_T`
|
||||
|
||||
Throw(e)
|
||||
--------
|
||||
|
||||
The method of throwing an error. Throws should only occur from within a
|
||||
protected (Try...Catch) block, though it may easily be nested many function
|
||||
calls deep without an impact on performance or functionality. Throw takes
|
||||
a single argument, which is an exception id which will be passed to Catch
|
||||
###Throw(e)
|
||||
|
||||
This is the method of throwing an error. A `Throw` should only occur from within a
|
||||
protected (`Try` ... `Catch`) block, though it may easily be nested many function
|
||||
calls deep without an impact on performance or functionality. `Throw` takes
|
||||
a single argument, which is an exception id which will be passed to `Catch`
|
||||
as the reason for the error.
|
||||
|
||||
If you wish to Rethrow an error, this can be done by calling Throw(e) with
|
||||
the error code you just caught. It IS valid to throw from a catch block.
|
||||
If you wish to rethrow an error, this can be done by calling `Throw(e)` with
|
||||
the error code you just caught. It **is** valid to throw from a catch block.
|
||||
|
||||
ExitTry()
|
||||
---------
|
||||
|
||||
On rare occasion, you might want to immediately exit your current Try block
|
||||
but NOT treat this as an error. Don't run the Catch. Just start executing
|
||||
from after the Catch as if nothing had happened... That's what ExitTry is
|
||||
###ExitTry()
|
||||
|
||||
On rare occasion, you might want to immediately exit your current `Try` block
|
||||
but **not** treat this as an error. Don't run the `Catch`. Just start executing
|
||||
from after the `Catch` as if nothing had happened... That's what `ExitTry` is
|
||||
for.
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
CONFIGURATION
|
||||
--------------------------------------------------------------------
|
||||
-------------
|
||||
|
||||
CException is a mostly portable library. It has one universal
|
||||
dependency, and some macros which are required if working in a
|
||||
@@ -172,75 +181,97 @@ multi-tasking environment.
|
||||
for each protected block and once during a throw. This is the
|
||||
only overhead in the system.
|
||||
|
||||
|
||||
Exception.h
|
||||
-----------------
|
||||
By convention, most projects include Exception.h which defines any
|
||||
further requirements, then calls CException.h to do the gruntwork. All
|
||||
of these are optional. You could directly include CException.h if
|
||||
-----------
|
||||
|
||||
By convention, most projects include `Exception.h` which defines any
|
||||
further requirements, then calls `CException.h` to do the gruntwork. All
|
||||
of these are optional. You could directly include `CException.h` if
|
||||
you wanted and just use the defaults provided.
|
||||
|
||||
EXCEPTION_T - Set this to the type you want your exception id's
|
||||
to be. Defaults to 'unsigned int'.
|
||||
* `EXCEPTION_T`
|
||||
* Set this to the type you want your exception id's to be. Defaults to 'unsigned int'.
|
||||
|
||||
EXCEPTION_NONE - Set this to a number which will never be an
|
||||
exception id in your system. Defaults to 0x5a5a5a5a.
|
||||
* `EXCEPTION_NONE`
|
||||
* Set this to a number which will never be an exception id in your system. Defaults to `0x5a5a5a5a`.
|
||||
|
||||
EXCEPTION_GET_ID - If in a multi-tasking environment, this should be
|
||||
set to be a call to the function described in #2 above.
|
||||
Defaults to just return 0 all the time (good for
|
||||
single tasking environments)
|
||||
* `EXCEPTION_GET_ID`
|
||||
* If in a multi-tasking environment, this should be
|
||||
set to be a call to the function described in #2 above.
|
||||
Defaults to just return `0` all the time (good for
|
||||
single tasking environments)
|
||||
|
||||
EXCEPTION_NUM_ID - If in a multi-tasking environment, this should be set
|
||||
to the number of ID's required (usually the number of
|
||||
tasks in the system). Defaults to 1 (for single
|
||||
tasking environments).
|
||||
* `EXCEPTION_NUM_ID`
|
||||
* If in a multi-tasking environment, this should be set
|
||||
to the number of ID's required (usually the number of
|
||||
tasks in the system). Defaults to `1` (for single
|
||||
tasking environments).
|
||||
|
||||
* `CEXCEPTION_NO_CATCH_HANDLER(id)`
|
||||
* This macro can be optionally specified.
|
||||
It allows you to specify code to be called when a Throw
|
||||
is made outside of `Try` ... `Catch` protection. Consider
|
||||
this the emergency fallback plan for when something has
|
||||
gone terribly wrong.
|
||||
|
||||
CEXCEPTION_NO_CATCH_HANDLER(id) - This macro can be optionally specified.
|
||||
It allows you to specify code to be called when a Throw
|
||||
is made outside of Try...Catch protection. Consider
|
||||
this the emergency fallback plan for when something has
|
||||
gone terribly wrong.
|
||||
|
||||
You may also want to include any header files which will commonly be
|
||||
needed by the rest of your application where it uses exception handling
|
||||
here. For example, OS header files or exception codes would be useful.
|
||||
|
||||
|
||||
Finally, there are some hook macros which you can implement to inject
|
||||
your own target-specific code in particular places. It is a rare instance
|
||||
where you will need these, but they are here if you need them:
|
||||
|
||||
CEXCEPTION_HOOK_START_TRY - called immediately before the Try block
|
||||
CEXCEPTION_HOOK_HAPPY_TRY - called immediately after the Try block
|
||||
if no exception was thrown
|
||||
CEXCEPTION_HOOK_AFTER_TRY - called immediately after the Try block
|
||||
OR before an exception is caught
|
||||
CEXCEPTION_HOOK_START_CATCH - called immediately before the catch
|
||||
|
||||
--------------------------------------------------------------------
|
||||
* `CEXCEPTION_HOOK_START_TRY`
|
||||
* called immediately before the Try block
|
||||
|
||||
* `CEXCEPTION_HOOK_HAPPY_TRY`
|
||||
* called immediately after the Try block if no exception was thrown
|
||||
|
||||
* `CEXCEPTION_HOOK_AFTER_TRY`
|
||||
* called immediately after the Try block OR before an exception is caught
|
||||
|
||||
* `CEXCEPTION_HOOK_START_CATCH`
|
||||
* called immediately before the catch
|
||||
|
||||
|
||||
|
||||
TESTING
|
||||
--------------------------------------------------------------------
|
||||
-------
|
||||
|
||||
|
||||
If you want to validate that CException works with your tools or that
|
||||
it works with your custom configuration, you may want to run the test
|
||||
suite.
|
||||
|
||||
The test suite included makes use of the Unity Test Framework. It will
|
||||
|
||||
The test suite included makes use of the `Unity` Test Framework. It will
|
||||
require a native C compiler. The example makefile uses MinGW's gcc.
|
||||
Modify the makefile to include the proper paths to tools, then run 'make'
|
||||
Modify the makefile to include the proper paths to tools, then run `make`
|
||||
to compile and run the test application.
|
||||
|
||||
C_COMPILER - The C compiler to use to perform the tests
|
||||
C_LIBS - The path to the C libraries (including setjmp)
|
||||
UNITY_DIR - The path to the Unity framework (required to run tests)
|
||||
(get it at http://unity.sourceforge.net)
|
||||
* `C_COMPILER`
|
||||
* The C compiler to use to perform the tests
|
||||
|
||||
* `C_LIBS`
|
||||
* The path to the C libraries (including setjmp)
|
||||
|
||||
* `UNITY_DIR`
|
||||
* The path to the Unity framework (required to run tests)
|
||||
(get it at [ThrowTheSwitch.org](http://throwtheswitch.org))
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------
|
||||
LICENSE
|
||||
--------------------------------------------------------------------
|
||||
-------
|
||||
|
||||
This software is licensed under the MIT License
|
||||
|
||||
Copyright (c) 2007-2016 Mark VanderVoord
|
||||
Copyright (c) 2007-2017 Mark VanderVoord
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,207 @@
|
||||
# ThrowTheSwitch.org Coding Standard
|
||||
|
||||
Hi. Welcome to the coding standard for ThrowTheSwitch.org. For the most part,
|
||||
we try to follow these standards to unify our contributors' code into a cohesive
|
||||
unit (puns intended). You might find places where these standards aren't
|
||||
followed. We're not perfect. Please be polite where you notice these discrepancies
|
||||
and we'll try to be polite when we notice yours.
|
||||
|
||||
;)
|
||||
|
||||
|
||||
## Why Have A Coding Standard?
|
||||
|
||||
Being consistent makes code easier to understand. We've made an attempt to keep
|
||||
our standard simple because we also believe that we can only expect someone to
|
||||
follow something that is understandable. Please do your best.
|
||||
|
||||
|
||||
## Our Philosophy
|
||||
|
||||
Before we get into details on syntax, let's take a moment to talk about our
|
||||
vision for these tools. We're C developers and embedded software developers.
|
||||
These tools are great to test any C code, but catering to embedded software has
|
||||
made us more tolerant of compiler quirks. There are a LOT of quirky compilers
|
||||
out there. By quirky I mean "doesn't follow standards because they feel like
|
||||
they have a license to do as they wish."
|
||||
|
||||
Our philosophy is "support every compiler we can". Most often, this means that
|
||||
we aim for writing C code that is standards compliant (often C89... that seems
|
||||
to be a sweet spot that is almost always compatible). But it also means these
|
||||
tools are tolerant of things that aren't common. Some that aren't even
|
||||
compliant. There are configuration options to override the size of standard
|
||||
types. There are configuration options to force Unity to not use certain
|
||||
standard library functions. A lot of Unity is configurable and we have worked
|
||||
hard to make it not TOO ugly in the process.
|
||||
|
||||
Similarly, our tools that parse C do their best. They aren't full C parsers
|
||||
(yet) and, even if they were, they would still have to accept non-standard
|
||||
additions like gcc extensions or specifying `@0x1000` to force a variable to
|
||||
compile to a particular location. It's just what we do, because we like
|
||||
everything to Just Work™.
|
||||
|
||||
Speaking of having things Just Work™, that's our second philosophy. By that, we
|
||||
mean that we do our best to have EVERY configuration option have a logical
|
||||
default. We believe that if you're working with a simple compiler and target,
|
||||
you shouldn't need to configure very much... we try to make the tools guess as
|
||||
much as they can, but give the user the power to override it when it's wrong.
|
||||
|
||||
|
||||
## Naming Things
|
||||
|
||||
Let's talk about naming things. Programming is all about naming things. We name
|
||||
files, functions, variables, and so much more. While we're not always going to
|
||||
find the best name for something, we actually put quite a bit of effort into
|
||||
finding *What Something WANTS to be Called*™.
|
||||
|
||||
When naming things, we more or less follow this hierarchy, the first being the
|
||||
most important to us (but we do all four whenever possible):
|
||||
1. Readable
|
||||
2. Descriptive
|
||||
3. Consistent
|
||||
4. Memorable
|
||||
|
||||
|
||||
#### Readable
|
||||
|
||||
We want to read our code. This means we like names and flow that are more
|
||||
naturally read. We try to avoid double negatives. We try to avoid cryptic
|
||||
abbreviations (sticking to ones we feel are common).
|
||||
|
||||
|
||||
#### Descriptive
|
||||
|
||||
We like descriptive names for things, especially functions and variables.
|
||||
Finding the right name for something is an important endeavor. You might notice
|
||||
from poking around our code that this often results in names that are a little
|
||||
longer than the average. Guilty. We're okay with a tiny bit more typing if it
|
||||
means our code is easier to understand.
|
||||
|
||||
There are two exceptions to this rule that we also stick to as religiously as
|
||||
possible:
|
||||
|
||||
First, while we realize hungarian notation (and similar systems for encoding
|
||||
type information into variable names) is providing a more descriptive name, we
|
||||
feel that (for the average developer) it takes away from readability and
|
||||
therefore is to be avoided.
|
||||
|
||||
Second, loop counters and other local throw-away variables often have a purpose
|
||||
which is obvious. There's no need, therefore, to get carried away with complex
|
||||
naming. We find i, j, and k are better loop counters than loopCounterVar or
|
||||
whatnot. We only break this rule when we see that more description could improve
|
||||
understanding of an algorithm.
|
||||
|
||||
|
||||
#### Consistent
|
||||
|
||||
We like consistency, but we're not really obsessed with it. We try to name our
|
||||
configuration macros in a consistent fashion... you'll notice a repeated use of
|
||||
UNITY_EXCLUDE_BLAH or UNITY_USES_BLAH macros. This helps users avoid having to
|
||||
remember each macro's details.
|
||||
|
||||
|
||||
#### Memorable
|
||||
|
||||
Where ever it doesn't violate the above principles, we try to apply memorable
|
||||
names. Sometimes this means using something that is simply descriptive, but
|
||||
often we strive for descriptive AND unique... we like quirky names that stand
|
||||
out in our memory and are easier to search for. Take a look through the file
|
||||
names in Ceedling and you'll get a good idea of what we are talking about here.
|
||||
Why use preprocess when you can use preprocessinator? Or what better describes a
|
||||
module in charge of invoking tasks during releases than release_invoker? Don't
|
||||
get carried away. The names are still descriptive and fulfill the above
|
||||
requirements, but they don't feel stale.
|
||||
|
||||
|
||||
## C and C++ Details
|
||||
|
||||
We don't really want to add to the style battles out there. Tabs or spaces?
|
||||
How many spaces? Where do the braces go? These are age-old questions that will
|
||||
never be answered... or at least not answered in a way that will make everyone
|
||||
happy.
|
||||
|
||||
We've decided on our own style preferences. If you'd like to contribute to these
|
||||
projects (and we hope that you do), then we ask if you do your best to follow
|
||||
the same. It will only hurt a little. We promise.
|
||||
|
||||
|
||||
#### Whitespace
|
||||
|
||||
Our C-style is to use spaces and to use 4 of them per indent level. It's a nice
|
||||
power-of-2 number that looks decent on a wide screen. We have no more reason
|
||||
than that. We break that rule when we have lines that wrap (macros or function
|
||||
arguments or whatnot). When that happens, we like to indent further to line
|
||||
things up in nice tidy columns.
|
||||
|
||||
```C
|
||||
if (stuff_happened)
|
||||
{
|
||||
do_something();
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### Case
|
||||
|
||||
- Files - all lower case with underscores.
|
||||
- Variables - all lower case with underscores
|
||||
- Macros - all caps with underscores.
|
||||
- Typedefs - all caps with underscores. (also ends with _T).
|
||||
- Functions - camel cased. Usually named ModuleName_FuncName
|
||||
- Constants and Globals - camel cased.
|
||||
|
||||
|
||||
#### Braces
|
||||
|
||||
The left brace is on the next line after the declaration. The right brace is
|
||||
directly below that. Everything in between in indented one level. If you're
|
||||
catching an error and you have a one-line, go ahead and to it on the same line.
|
||||
|
||||
```C
|
||||
while (blah)
|
||||
{
|
||||
//Like so. Even if only one line, we use braces.
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### Comments
|
||||
|
||||
Do you know what we hate? Old-school C block comments. BUT, we're using them
|
||||
anyway. As we mentioned, our goal is to support every compiler we can,
|
||||
especially embedded compilers. There are STILL C compilers out there that only
|
||||
support old-school block comments. So that is what we're using. We apologize. We
|
||||
think they are ugly too.
|
||||
|
||||
|
||||
## Ruby Details
|
||||
|
||||
Is there really such thing as a Ruby coding standard? Ruby is such a free form
|
||||
language, it seems almost sacrilegious to suggest that people should comply to
|
||||
one method! We'll keep it really brief!
|
||||
|
||||
|
||||
#### Whitespace
|
||||
|
||||
Our Ruby style is to use spaces and to use 2 of them per indent level. It's a
|
||||
nice power-of-2 number that really grooves with Ruby's compact style. We have no
|
||||
more reason than that. We break that rule when we have lines that wrap. When
|
||||
that happens, we like to indent further to line things up in nice tidy columns.
|
||||
|
||||
|
||||
#### Case
|
||||
|
||||
- Files - all lower case with underscores.
|
||||
- Variables - all lower case with underscores
|
||||
- Classes, Modules, etc - Camel cased.
|
||||
- Functions - all lower case with underscores
|
||||
- Constants - all upper case with underscores
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
Egad. Really? We use markdown and we like pdf files because they can be made to
|
||||
look nice while still being portable. Good enough?
|
||||
|
||||
|
||||
*Find The Latest of This And More at [ThrowTheSwitch.org](https://throwtheswitch.org)*
|
||||
Vendored
+1
-1
Submodule vendor/unity updated: c3658a065d...2c7629a0ae
Reference in New Issue
Block a user