Activity

penguinencounter

We need to redesign the i/o UX. People shouldn’t have to scroll up and down the page just to figure out which input file correlates with which output file. As a result, we’ve switched the page to grid, and are using elements that span the three columns to contain the wider stuff (like the i/o panel.)
The recipe area now needs to go below both the inputs and outputs, but it should still be visible initially (in the case of shared links, this is especially useful.)

Also, added an error handler that pops up a notification.

Attachment
0
penguinencounter

i regret to inform you that i had a massive skill issue making this notification with progress bar.

Attachment
Attachment
0
penguinencounter

okay so we need to redo the UI a bit. people expect horizontal flows when doing conversions, and having to scroll to correlate files will be a problem.

also, notifications for download progress coming soon?

also also we had to disable the service worker because it was making changes not happen in dev which was very annoying

Attachment
0
penguinencounter

oh wow i forgot to devlog

we implemented like half of the recipe system. recipes are like scripts but not as good because we’re interpreting them with JS
planned step types:

  • ffmpeg
  • ffprobe
  • set output file (done)
  • bulk set variables (done)
  • regex replace (done)
  • regex match

i also added a ServiceWorker for caching because ffmpeg is like 30 MB. plan is to also have it get that cross-origin isolation so we can use the multithreaded ffmpeg library
(you’re supposed to look at the ‘transferred’ column in the screenshot)

Attachment
0
penguinencounter

we now support files that aren’t just audio! like images! and videos! just look at the image!

(ffprobe to the rescue here, we’re just parsing the output of ffprobe -v error -show_entries stream $FILE for each file)

Attachment
0
penguinencounter

i really hate bundlers.

the ones that work work well, except when you need a worker.
and then they all fail.

so we’re resorting to manually copying files out of node_modules.

and patching our output JS

funnnnnn
(in other news, I made Forgejo CI push to GitHub in order for hosting to happen on GitHub pages. try it out @ https://penguinencounter.github.io/fumux-prod/dev/)

did I mention I did a bunch of UI design too? that also happened.

Attachment
Attachment
0
penguinencounter

made the drag and drop work! for some reason tampermonkey was not cooperating with it but then it started working so IDK what happened there

the clear button does not work and selecting files does not work

fun fact .ogg is application/ogg and not audio/vorbis. .oga might work, but we need to support .ogg

Attachment
0
penguinencounter

i got it to read files and tell me if they’re stereo or mono as well as the bitrate.

this is ffprobe -v error -select_streams a -of default=nw=1 -show_entries stream=bit_rate,channels input.ogg -o input-data.txt under the hood, and we’re using JSPM to link our packages (thanks to Lea Verou’s blog for introducing JSPM to me, it uses importmaps which are nice because it’s not a freaking bundler)

Attachment
Attachment
0
penguinencounter

Small one:

Avatar initialization works and doesn’t get misattributed to some random event!

(Avatar init is pushed onto an event queue and kinda gets ran whenever it can, meaning it previously got lumped into something random, which led to some poor POST_WORLD_RENDER bearing the burden of an entire avatar’s initialization scripts…)

To do this we had to defer creating an event until we knew what it was, but also support going back and retroactively deciding we had no idea what it was.

Attachment
0
penguinencounter

Finished off the branding work! I have a 16:9 banner and a 1:1 icon, both with absurdly large resolutions (8K for the banner, 4320x4320 for the icon)

(I also have some images of just the wireframe scene and just the full scene, but they don’t have attribution in them so I’m not going to upload them)

🎬 Video processing — check back later!

1

Comments

penguinencounter
penguinencounter 21 days ago

if the attachment is still broken by the time this gets shipped, pretend I just attached the banner and icon and https://lapse.hackclub.com/timelapse/QvbzwNj61766 as attachments

penguinencounter

more branding! ft. lapse

i’m using these avatars (you need to be in the FiguraMC discord to use these):

some time was taken trying to figure out why HIPRT wasn’t working in blender, but that’s a problem for later because it seems like I’ll need to compile blender from sources to fix it

0
penguinencounter

Code

Added a /fprofile command to control recordings from in-game and filter them to a specific player. There are still some bugs with the filtering where events aren’t being parented correctly to work out, however.

also, I made a instruction decoder in SQLite. this will be useful somehow, I will make sure of it! (please be faster than the relevant data transfer speeds 🙏)

Lapse

I worked on some branding (trying to make the project banner happen!)
Check it out below I guess

Attachment
0
penguinencounter

okay maybe we shouldn’t have gotten too excited because all the parenting was wrong.
that should be fixed now though? all it took was even more views and more trigger fanagling yep

I learned how to make SQLite error if a condition is met though, that was useful for debugging because otherwise I can’t really get any debugging info out of my triggers, I just have to hope they work lol
it’s something like this (FAIL makes it so that things don’t get rolled back):

SELECT CASE
           WHEN (SOME CONDITION)
               THEN RAISE(FAIL, 'AAAA debug data: ' || WHATEVER)
           END;

(attachment 1: Select id, parent, type, friendlyName, realtime from tree where parent = 1172;. no, there should not be 500+ frames nested inside this one frame.)
(attachment 2: after I fixed it; how it’s supposed to look when doing that CTE from earlier)

Attachment
Attachment
0
penguinencounter

really quick update to note that it worked, letsgooooo


look at these numbers! on average, it’s 46% faster than the old trigger method and 57% faster than two INSERTs!
and at the 99.5%ile it blows past the original trigger, being 70% faster, and it’s even faster than the two-inserts method as well!

…now we just need to check that everything got parented… yep! looks like we’re keeping this one!

new data is in the same place (this spreadsheet).

Attachment
0
penguinencounter

i absolutely locked in on SQL and cooked up this monstrosity: a SQL-backed stack to avoid doing data transfer as much as possible. we will see if it works in the next devlog

CREATE TABLE IF NOT EXISTS temp.stack (
    ordering INTEGER NOT NULL PRIMARY KEY,
    id       INTEGER NOT NULL,
    type     TEXT    NOT NULL
) STRICT;

you can push new items to it like this:

INSERT INTO stack (ordering, id, type)
VALUES (
    coalesce(
        (SELECT ordering - 1 FROM stack ORDER BY ordering LIMIT 1),
        0
    ), last_insert_rowid(), 'frame'
);

peek the top item:

SELECT id FROM stack ORDER BY ordering LIMIT 1

and pop it:

DELETE FROM stack ORDER BY ordering LIMIT 1
-- in our case we want to unwind in case some internal state gets messed up
DELETE FROM stack
WHERE ordering <= (
    SELECT ordering FROM stack WHERE id = old.id LIMIT 1
);

for the attachment i’ve run this INSERT repeatedly to show how the stack table and triggers automate a bunch of bookkeeping.

INSERT INTO fast_events (timestamp, friendlyName, owner, category)
VALUES (1,
        'Test event',
        '497dcba3-ecbf-4587-a2dd-5eb0665e6880',
        'undefined');
Attachment
0
penguinencounter

the worst part about rewriting your architecture is actually implementing your architecture.

did you know it takes (relatively) AGES to do an INSERT ... RETURNING + INSERT on the application layer, compared to the equivalent TRIGGER instructions_insert ON instructions INSTEAD OF INSERT which uses last_insert_rowid? yeah me neither but it’s bad as shown by this profiler output (profiling a profiler. hmmmm)
full resolution image

that’s all for now, i’ll go back to trying to figure out how to use a trigger for everything. it looks like sqlite-jdbc is doing a bunch of stuff i don’t want it to do during initial setup smh

edit: want some data? here you go: raw performance data in nanoseconds
warning: RAW DATA sheets have a ton of rows that your browser probably won’t like (as in 450,000ish!)

Attachment
0
penguinencounter

who likes redesigns? we like redesigns! (a bit too much, probably)

we gave up on the old schema because of two big issues:

  • we want to make beautiful waterfall charts, but the relation between instruction steps and the calls that they’re a part of are very unclear
  • calls should be part of a tree, not flat? like, this seems obvious but we somehow missed it the first time

so… pure tree structure! (also I added inheritance to the schema so that each individual node is slightly smaller)
I also added some views that do the filter and LEFT JOIN automatically, as well as some INSTEAD OF INSERT triggers to make inserting into views work the way it’s supposed to.

this does mean doing a bunch of work keeping track of relations.

also, have a recursive cte to get the root node!

WITH RECURSIVE parents(id) AS (SELECT ?
                               UNION ALL
                               SELECT tree.parent
                               FROM tree
                                    JOIN parents ON tree.id = parents.id)
SELECT tree.*
FROM tree
     JOIN parents ON tree.id = parents.id
WHERE tree.parent IS NULL;

click to view full-resolution diagram!

Attachment
2

Comments

penguinencounter
penguinencounter 27 days ago

oh no it’s really crunchy

penguinencounter
penguinencounter 27 days ago

Oops, realized I needed a timestamp field on my events, frames, and calls, so that’s in the schema for the tree table as well. It’s INTEGER DEFAULT NULL.

penguinencounter

Sources are real! Also, I can now throw queries like this at the database…

SELECT z.id,
       e.label               as event,
       sum(z.insn)           as totalInsn,
       sum(z.realtime) / 1e9 as totalTime,
       s.name                as source,
       z.lineNumber          as line,
       z.pc                  as pc,
       c.bytecode,
       e.owner               as owner,
       s.content
FROM steps z
         LEFT JOIN events e on z.event = e.id
         LEFT JOIN main.closures c on z.closure = c.id
         LEFT JOIN main.sources s on z.source = s.uuid
WHERE e.parent = 22876
   OR e.parent = 22812
GROUP BY z.event, z.source;

(instruction & time usage per source & event, plus some more data)

there are still performance issues, however. I need to compare against a baseline

Attachment
Attachment
0
penguinencounter

We now have some actual event hierarchies! We did lose the source attribution while we’re preparing to staple sources, though.

Completed

  • Indexes are now taking up 70% (!!!) of the database size, somehow!
  • Also, implemented autocommitting. This is much more space-efficient than keeping everything in the WAL, wow
  • Events are real!
  • Steps are associated with events!
  • Actually figured out what I’m doing wrt prepared statements! (turns out it just needed another abstraction layer)
  • Found a quirk in Figura that means that animation keyframes run before everything else in the game. Hmmm

To fix

  • timestamps on root events are very broken
  • timestamps in general make very little sense
  • drop owner column from steps because it can be derived by crossreferencing with events
  • figure out what indexes we actually need and drop the rest (again, 70% of disk space!?!?)

To add

  • Sources!
  • Filter recordings. We really don’t want to be recording everyone on plaza and blowing through our users’ disk space.
  • Capture pings
  • More stuff scattered throughout the codebase in //TODO comments
Attachment
0
penguinencounter

We have a minimum viable product! (very minimum and very not viable)

It just dumps all of the instructions of all loaded avatars with no distinguishing between them. Also, it eats disk space for breakfast and doesn’t commit ever, so the -wal file just grows until you quit the game.

This gave us the opportunity to see the index creation performance. It seems to roughly double(!) the database size (!!) and takes about 5 seconds for ~2 min of recording on Plaza.

aside: do database screenshots count as “not code”? because i don’t have like, UI work to show for any of this…

i guess you get to have a visualization as a treat

select source, count(*) as total from steps group by source order by total desc;
Attachment
Attachment
0
penguinencounter

oh we’re so back

Not Databases

  • fixed a bug where the game wouldn’t launch in dev environments because of a transitive dependency usurping the direct dependency
  • added ‘caused by’ support for translatable exceptions

Databases

  • made DbAction for individual processes
  • sketched out schema again
  • we now run the schema directly from a .sql file instead of having to type it into a .kt file
  • indexes are created in an ‘optimize’ action instead of when creating the tables because indexes are slow
  • databases can be opened read-only
  • foreign keys start disabled by default and are only enabled & checked upon recording completion
Attachment
0
penguinencounter

Made a LineEdit wrapper that supports custom padding around the actual LineEdit.

features:

  • inspector support for managing all the styles on the root element
    • uses the theming & theme variation system!
  • live preview
  • focus & readonly support

not-yet-features:

  • click on panel to focus
  • other LineEdit properties
Attachment
0
penguinencounter

I’m making a launcher (like a thing that launches apps!)

It doesn’t have a name yet.

For now, I’ve decided to try using Godot 4 to do it because it

  • comes with built-in support for transparent windows
  • also supports wayland, kind of
  • has a pretty good UI toolkit too

special thanks to popcar for these blog posts:

note: cyan box will not be in final product. it will either be black or a shader. i also really want this to be customizable

Attachment
0
penguinencounter

did you know there’s a 2000 character limit on devlogs? I sure didn’t until after I had written a really big one with code examples and then it rejected me.


tldr: rewrote database wrapper. added SQLite WAL support. tried to fix concurrency. found out the hard way that you really need retries on SQL operations.


the whole thing, for all the storytelling score:
https://gist.github.com/penguinencounter/1ec89dc025b8b400c61517d37467728b


also, the reason why the attachment is weird: https://github.com/hackclub/flavortown/issues/1086
(I have to upload a new copy of that image every time I edit the text :P)

Attachment
0
penguinencounter

Did some database design:

  • profile_meta table; single row, contains schema versioning information

  • sources: source attachments

  • channels: groups that users may want to track, like TICK, or RENDER. could be continuous or not

  • events: collections of steps, like a single tick or frame. instruction count and timing are totaled under a single event to measure overall performance / permission level impact

    • can have a parent event (ex. ITEM_RENDER has RENDER as a parent)
  • steps: individual actions within a script. instructions, function calls, detailed timings

  • Added method to automatically set up a blank database with the current schema version.

  • Added method to apply database migrations in the future.

  • Added method to copy a on-disk database into a temporary database

    • this is done with a RESTORE FROM command, which is not real SQLite and gets intercepted by the JDBC connector. As a result, you can’t do this with a prepared statement, so manual sanitization goes brrrr
newConn.createStatement().use {
    val syntax = when {
        url.contains("\"") && url.contains("\'") -> throw SQLException("Cannot unmount database: no safe quote type for $url")
        url.contains("\'") -> "RESTORE FROM \"$url\""
        else -> "RESTORE FROM '$url'"
    }
    @Suppress("SqlSourceToSinkFlow")
    it.executeUpdate(syntax)
}
Attachment
0
penguinencounter

Project Setup

Created a new project based on https://github.com/FallingColors/hexdummy.
This is a Hex Casting addon template, but it has the best Architectury+Kotlin build system I’ve seen so far, so we’re going to use it for something completely unrelated.

  • tore out all the Hex Casting stuff
    • kept PKPCPBP for the JSON5 preprocessor
  • added Figura dependencies
    • figura itself - figura-common & figura-fabric & figura-forge
    • its transitive dependencies, figura-luaj-core, figura-luaj-jse, nvwebsocketclient, oggus, concentus, and fabricPermissionsApi
  • updated MixinExtras
  • upgraded Loom to 1.13
    • build JDK must be at least 21
    • upgraded kotlin to 2.2.21
  • added SQLite JDBC library (plan is to make profiler files SQLite databases)

Actual Mod Code

  • added hooks for the start and end of function calls, as well as every instruction executed in Lua bytecode
  • found out that LuaJ’s LuaClosure::execute method has bad control flow and causes @Expression from MixinExtras to get stuck in an infinite loop for some reason??
Attachment
Attachment
1

Comments

developergirishgiri
developergirishgiri about 2 months ago

nice plz give me vote also and follow me

penguinencounter

More matching libraries! In this case it’s DotNetNuke.Log4net. Having a quick way to see all the logging calls is nice

Attachment
0
penguinencounter

Found a matching library!

HidLibrary appears to be statically linked into the executable.

Attachment
0
penguinencounter

ILSpy decompilation accuracy is much better than dotPeek’s, so I switched all the sources to that. As a result, I can actually build the application from the decompiled sources! (It doesn’t run ofc but it’s not incorrect at least)

That does mean I lost some of the renames. Though, I am keeping track of the original names of everything now, so in case of some decompiler issues it doesn’t become a pain to track things down.

Attachment
1

Comments

penguinencounter
penguinencounter 2 months ago

why is it so crunchy

penguinencounter

Mapped a bunch of Windows API bindings and names. Also, figured out that C# async is completely fake and is just 2 delegates and 3 methods in a trenchcoat.

Attachment
Attachment
1

Comments

penguinencounter
penguinencounter 2 months ago

This is an example comment

penguinencounter

it works! kind of! (well, it doesn’t crash?)

needs: UI, actual merged output, conflict resolver

Attachment
0
penguinencounter

more untested code! to be tested tomorrow

(it’s like an event loop or something. i am coming to the realization that i will somehow need to write developer documentation for all of this)

in other news I turned full line code completion back on, we’ll see if it actually saves any time

Attachment
Attachment
Attachment
Attachment
1

Comments

penguinencounter
penguinencounter 3 months ago

why didn’t this show up in the feed channel

penguinencounter

Is it binary? The answer may surprise you.

we have two different kinds of UTF-16 and you have to guess which one is correct

also JS labels are real and you Can use them

Attachment
Attachment
Attachment
0
penguinencounter

built out the planner system a bit. should be able to actually pick the right before transformer by next devlog

also we’re just pretending everything we don’t recognize is text for now. this is a bad idea

Attachment
0
penguinencounter

JavaScript is a good and well-designed language /s
(maps to the rescue)
(this is a bug fix devlog)

Attachment
0
penguinencounter

I made a Meyers diff implementation!

Special thanks to James Coglan for this set of 3 articles which makes the algorithm make sense (or, more sense than I got from reading the original paper) and also providing a Ruby implementation that I adapted to TypeScript:
https://blog.jcoglan.com/2017/02/12/the-myers-diff-algorithm-part-1/
https://blog.jcoglan.com/2017/02/15/the-myers-diff-algorithm-part-2/
https://blog.jcoglan.com/2017/02/17/the-myers-diff-algorithm-part-3/

I wish I could get hackatime time for the hour spent reading math papers to figure out how the hell all these algorithms work. oh well

Next: probably a basic 3-way merge, then a MVP

(Also, Rhythm Doctor OST is carrying me through this)

Attachment
Attachment
1

Comments

penguinencounter
penguinencounter 3 months ago

oops their name is Myers not Meyers. how did I not notice this

penguinencounter

a bit of polish

(yeah it’s 47 minutes of frontend. time to lock in on backend again)

Attachment
1

Comments

penguinencounter
penguinencounter 3 months ago

also you can’t see it because it’s a still image but I animated the progress bar

penguinencounter

made a quick browser demo for testing purposes (and finished up getting back to before I deleted everything)

next is probably getting text merges to work

Attachment
0
penguinencounter

Got the project set up and published on GH.

Also, accidentally made a circular dependency when trying to make a Promise wrapper with a progress callback. That was interesting to solve. (Builder to the rescue?)

(There’s not really much to show, because most of this is library stuff - eventually there will be a browser demo, but we’re not there yet)

Attachment
0