been working on an airship for far too long
progress kind of died down, but i hope to finish it with some nice scenery sooner or later
here’s the place with the airship:
been working on an airship for far too long
progress kind of died down, but i hope to finish it with some nice scenery sooner or later
here’s the place with the airship:
That engine is impressive
Messed around with the idea of 3D animated particle effects with a simple smoke effect. It’s much too pronounced for something like landing from a jump, but it was an easy way to try it out.
thanks, they were a huge pain.
the big engine is based on a sulzer engine from 1914, and has around 1.3k parts.
the two smaller engines are fighter engines from ww1. together they have around 900 parts.
they aren’t accurate at all, but i just keep adding stuff until it’s too complex to make sense of.
Because you said you thought the art was “super cool” , thought you’d be interested lol
Motivation’s started to slip. I’m worried that I’ll just leave this place at 80% like the rest of my builds. Anyway, here’s a sign, now with words!
Was going to put this in its own thread, hence the length, but it’s not worth that, so…
Disclaimer: This is not very useful for reasons that will become clear momentarily. Nevertheless, it’s fun.
So it turns out if you pass error
a non-string value, then xpcall
the function calling error
, the xpcall
handler gets the actual value passed to error.
Having learned this, I made exceptions, for…some reason.
Sadly they’re rather limited because xpcall
is distinct from ypcall
and cannot yield in the invoked function (the handler receives attempt to yield across metamethod/C-call boundary
).
I implemented exceptions as a single module, shoehorning in a try/catch equivalent. You construct an exception using Exception.new
, which has the following signature:
Exception Exception.new(string message, string exceptionType = "Exception", Exception inner = nil)
There’s also a shorthand Wrap
method that wraps the exception it’s called on in a new exception.
I tossed a try/catch equivalent together too; here’s a rudimentary use:
local Exception = require(script.Parent.Exception)
local function Trim(a)
if type(a) ~= "string" then
error(Exception.new("Type mismatch: Got "..typeof(a)..", expected string.", "ArgumentException"))
end
return a:match("^%s*(.-)%s*$")
end
Exception.Try(Trim, 1, {
[{ "ArgumentException" }] = function(err)
print("Caught: "..err.Message, "\n", err.StackTrace)
end
})
The signature for Exception.Try
is a mouthful, but here we go:
void Exception.Try(function tryFunc, Tuple<Variant> args, Dictionary<Array<string>, function<Exception>>)
It takes a function to invoke as the “try” function, a variable number of arguments, and a table of handlers keyed by an array of exception types to handle. It’s ugly, I know.
Source of the exception module:
local Exception = {}
Exception.__index = Exception
Exception.Type = "Exception"
function Exception.new(message, exceptionType, inner)
return setmetatable({
Message = message;
Inner = inner;
Type = exceptionType;
StackTrace = debug.traceback();
}, Exception)
end
function Exception.Try(func, ...)
local args = { ... }
local handlers = args[#args]
table.remove(args, #args)
local success, details = xpcall(function() func(unpack(args)) end, function(err)
if type(err) ~= "table" then
return Exception.new("Lua error: "..tostring(err))
end
for types, handler in pairs(handlers) do
for _, exceptionType in ipairs(types) do
if exceptionType == err.Type then
handler(err)
return { true, err }
end
end
end
return { false, err }
end)
if not success and not details[1] then
-- Toss the exception upwards.
error(details[2])
end
end
function Exception:Wrap(message, exceptionType)
return Exception.new(message, exceptionType, self)
end
function Exception:__tostring()
local message = {
self.Type,
" - ",
self.Message,
}
if self.Inner ~= nil then
table.insert(message, "\n")
table.insert(message, tostring(self.Inner))
end
return table.concat(message, "")
end
return Exception
Good news! Exceptions are fast in the best case scenario - that is, nothing errors. No exception is thrown. This is because exceptions are an extension built on error
and xpcall
- if error
is never called, xpcall
never invokes the handler function, so no (comparatively) slow error processing Lua code gets run.
If an exception is thrown, however, performance can rapidly degrade. The example code above averages 0.65ms to catch the thrown ArgumentException
; I’ve not done larger-scale tests with this to see how much of it is constant factors and how much is the two loops through the handlers dictionary.
This is kind of a messy implementation; I’m not likely to clean it up because of the yielding issue (also because this is complete and total overkill for most cases - pcall
and string error
calls are probably all you need, if that).
I tried using pcall
to allow exceptions to yield; pcall
doesn’t like the error being a non-string. The second return value is the rather unhelpful string An error occurred
instead of anything I can work with. I could error with a JSON encoding of the exception and decode it in the try/catch function but that’s reaching extreme levels of misdirection
Heres an update on this.
This was the first time I was able to manually compile and setup the materials for an R15 character, using the resources my program was able to export for me. All I had to do was convert the textures over to the VTF format, and I had to compile the character into the .MDL format
I was able to fetch the R15 character texture from my 3D thumbnail on the website. There were specific regions on the character’s texture map, that I was able to split up into individual textures. As for the hats, I just fetched their textures from the meshes themselves. I still don’t have this working for R6, and I’m afraid I might have to generate the R6 texture maps myself, which will be M I L D L Y _ P A I N F U L, but we’ll see how that goes.
Theres still a lot of backend stuff I have to do to completely automate the compilation, and I still need to make the UI for the program. I’m expecting to be done before the end of the month though!
If you need someone to make a gui In C# let me know
Just don’t use xpcall and use pcall.
You can’t pass the literal value, but you could pass a unique string that can be pointed to the exception.
(and to make it look good, have it be the error message, followed by random invisible characters)
Also, in Exception.Try, you should put the handler table before the tuple.
Now you’re blocking varargs and functions that return multiple values.
(you also drop trailing nils, but apparently I’m the only person on ROBLOX that cares about that)
still nice though. I like anything (semi-)haxy in Lua
I could pass an ID but then it requires storing state, and that’s…more effort than I want to go to (and also doesn’t fulfill the point of that implementation, which was to do something with xpcall
and error
calls with non-string values). Try
swallows all return values of the trying function as well as the handlers; that’s somewhat intentional.
Storing state is just dropping it in a table and reading it if the Try thing requests it.
I wonder why pcall just does “An error occured” while xpcall doesn’t.
(granted, xpcall just does return handler(errorMessage)
, but still, weird pcall needs a string)
You also have to deal with garbage collection; a weak-key table won’t work because strings are interned, so there’s always a reference to them somewhere (I’m not sure whether the intern references prevent garbage collection, though - they might not). Weak-value won’t work either because there are valid cases where an exception has no references to it (right when it’s being thrown for example), so you’d randomly lose exceptions (and wouldn’t that be hard to debug :s) when garbage collection happens. If you use a strong table you get a memory leak because exceptions won’t ever be garbage collected even after they’re thrown / caught.
Theoretically try/catch could free the references, but that introduces a hidden dependency on Try
that would be…annoying (“use this function or you will eventually crash your server via a memory leak!”).
Yeah, I’m not sure what’s up with that - I would have expected table: [some memory address]
or something, not An error occurred
(unless it’s somewhere in pcall
itself that’s throwing An error occurred
).
Yeah, I wasn’t entirely sure either if using strings as weak keys would work.
I assumed that, as long as the error message is somewhere (e.g. inside the Try) it won’t get collected.
I’ll do a quick test now I guess.
EDIT: Can’t get strings (inside a weak table) to GC
I always thought GC happened between ticks?
You could probably do something haxy, storing the data in a (regular) table, which is stored in a weak table with the coroutine that called Try as (weak) key. But that’s a bit… ill adviced…
EDIT: While writing the above, I thought of a small issue, but completely forgot to write it down:
pcall won’t allow you to get the error stacktrace, as debug.traceback() will run in another stack.
now if xpcall(coroutine.wrap(func)) worked… (which doesn’t work, tried that a long time ago already)
Yeah; I suppose if you have a poorly-placed yielding operation you could get a garbage collection between error creation and throwing. Dunno. It’d take some work.
On another note…soontm
the mechanicals on that are absolutely dope
Where’s the sea anemone?
Did you make any of that in Blender, or is it all unions?