Programming thread -

Least Concern

Pretend I have a vtuber avatar like everyone else
kiwifarms.net
Hello CodeNiggers, How do you usually study theory/theoretic topic? MIT OCW and coursera? Books? Diving into a practical application of said topic?
The latter. I have to actually use something to really learn it; otherwise it's just my eyes running over a page. Failing that, taking notes helps for me. Something about rewriting what I'm reading in my own words helps me remember it; maybe it's because I have to slow down and think about it for longer than I would if I'm just reading over it. That's just me, though.
 

Rusty Crab

and it kept getting worse...
kiwifarms.net
working on some unity shit for fun. learning about meshes. i know nothing about rendering so it's making me unhappy.
On that note, I've been finding shaderlab absolutely miserable to learn due to the vast sea of low quality information and fragmented tutorials about it. I've found a series of 5 articles this morning that were immensely helpful. There's functional, complete and simple examples that aren't 30 pages of jibberish and pragma statements.
 

Null

Ooperator
kiwifarms.net
Nice, it looks like Unity has updated quite a bit since I've last used it. I'm interested to see what you create.
yeah yeah null is making kiwi 64
sir I will have you know I am making cubes.

1626815946577.png
 

Knight of the Rope

True & Honest Fan
kiwifarms.net
Failing that, taking notes helps for me. Something about rewriting what I'm reading in my own words helps me remember it; maybe it's because I have to slow down and think about it for longer than I would if I'm just reading over it. That's just me, though.
That's everyone. I've only ever met one guy that could instantly read something once and perfectly memorize it, and he was on the low-end of the high-functioning autism spectrum and was basically just an encyclopedia (sure he knew all the facts and could regurgitate rote exercises, but ask him what they all meant, or how to put them together, or how to apply the knowledge in a novel scenario? Then he was fucked).

There's a reason note-taking has been a thing in schooling since forever.

That said, I agree about coding being the best way to learn to code. More than any other 'hard' subject, you only really get better at coding by doing it: making programs, getting compile errors, fixing bugs, wrangling projects together, and so on.
 

Null

Ooperator
kiwifarms.net
1626864064357.png

i made a cube mesh manually with ecs. It's five-faced, so basically a platform with a skirt. My goal is to make it so that all contiguous tiles get baked into a single mesh, with the four faces on the outside only rendering around the exposed edges. from there, the next step would be to reduce the number of triangles.
 

Null

Ooperator
kiwifarms.net
Isometric/3D tile system I'm guessing?
yes

I'm currently learning I am a stupid retard nigger because trying to draw triangles for this is making me want to kill myself

1626874511254.png

the benefit of doing Unity ECS is that you don't get a lot of "wow you want to do tilemaps? have you bought Retard Nigger Gaming's Deluxe Tilemap package for $60?"
 

Indefinite_Ordered_Sets

If specified, this will replace the title that dis
kiwifarms.net
working on some unity shit for fun. learning about meshes. i know nothing about rendering so it's making me unhappy.
Ditch that shit, go straight to Unreal. Every game engine today streamlined to almost the same under the hood, so why not already get the best of free and don't wast your time on mediocrity. Unreal appears more complex than it actually is, and Unity, on the contrary, appears more simple. Plus Unreal is open source, and in my opinion made a good decision to get rid of intermediate scripting language in favor of simplistic visual scripting. If you need some straightforward and standard game logic - visual scripting will satisfy that need without complication of syntax, semantics and other shenanigans of traditional written computer language. If you need performance, you cannot find anything better than C++, in which is written the very engine. When you introduce intermediate scripting language for your game engine, you will get the slowness and memory hunger plus all the intricacy of language constructs and their written syntax, so basically the worst of both worlds.
 

WeWuzFinns

Armed with the cap of flame and goggles of speed.
kiwifarms.net
visual scripting
Computers went to shit when they started making them for niggers

I'm currently learning I am a stupid retard nigger because trying to draw triangles for this is making me want to kill myself
just watch few Blender tutorials and in a few days you are making your very own beautiful engineering graphics
 

Dick Justice

If you say "normie" you are that which you condemn
True & Honest Fan
kiwifarms.net
Does anyone wonder if it's not the cube that's rotating, but you orbiting around it and by extension what the mass, volume, and orbital distance would have to be to create that stable orbit?
(there is no spoon)
 

Null

Ooperator
kiwifarms.net
1626901248083.png

ok behold. doesn't look like much change but I've completely reworked the entire fucking grid system I had so that they're using structs as uber-effeciently as possible.

I have an idea for reducing the number of verts and tris with some quick code. my gambit looks like this:

1626901366027.png

I was trying to figure out a way to do it very specifically but that's not going to happen. I'd need a proper lib and I'm not quite ready to start importing 3rd party code wholesale. The laziest way is to just do each cell but if I can do it by column like this I'd still save a huge number of verts without needlessly complicating the code and with avenues for improvement in the future.

I've completely removed the tiles from the entity system. They're serialized in blob data. When I start building the third dimension I'm going to do it that way as well.
 

ConcernedAnon

Concerned and hopefully anonymous
kiwifarms.net
working on some unity shit for fun. learning about meshes. i know nothing about rendering so it's making me unhappy.
Jump straight into vulkan for a good time

View attachment 2368036

ok behold. doesn't look like much change but I've completely reworked the entire fucking grid system I had so that they're using structs as uber-effeciently as possible.

I have an idea for reducing the number of verts and tris with some quick code. my gambit looks like this:

View attachment 2368040

I was trying to figure out a way to do it very specifically but that's not going to happen. I'd need a proper lib and I'm not quite ready to start importing 3rd party code wholesale. The laziest way is to just do each cell but if I can do it by column like this I'd still save a huge number of verts without needlessly complicating the code and with avenues for improvement in the future.

I've completely removed the tiles from the entity system. They're serialized in blob data. When I start building the third dimension I'm going to do it that way as well.
Honestly, just start with the lazy tile by tile method first and upgrade later. For most of your probable cases fill-rate (per pixel cost) is going to be more important than transform-rate (per vertex cost) —unless your tiles are like pixel scale or something.
Gotta get things running so you can experiment and figure out what your goals and requirements are.



Recently I've been rewriting a concurrent scheduler I wrote a year ago, the goals are low latency, high concurrent throughput, and low gc overhead. To the gc end, I've been pooling jobs through this weird TypedList container I came up with, the rough idea is that the TypedList maps numeric ids to objects in pools of strongly typed storage, so you can retrieve them later, or even apply functions to them without knowing the type of object, all this without lifting anything onto the heap.

C#:
public class TypedList // Shitty abbreviated non-concurrent version
{
    public interface IPool // Stores an array of some type, and allows non-boxing access
    {
        Type type { get; }
       
        bool TryRead<T>(int idx, out T value);
       
        bool TryWrite<T>(int idx, in T value);

        bool TryUpdate<T>(int idx, in T value);

        bool TryRemove(int idx);
       
        int Add<T>(in T value);
    }

    public class Pool<T> : IPool
    {
        public Type type => typeof(T);
       
        public bool TryRead<TOut>(int idx, out TOut value)
        {
            if (values_[idx].valid && values_[idx].value is TOut v)
            {
                value = v;
                return true;
            }
            value = default;
            return false;
        }

        // ...

        private List<(T value, bool valid)> values_ = new(); // Strongly typed storage, no lifting to the heap required
    }

#region Internal

    private virtual IPool CreatePool(Type type) // Override to add extra functions to pools
        => typeof(Pool<>).MakeGenericType(type).GetConstructor(Type.EmptyTypes).Invoke(null);

    private int EnsurePool(Type type) // Get a pool for type
    {
        if (!typeMap_.TryGetValue(type, out var i))
        {
            i = pools_.Count;
            pools_.Add(CreatePool(type));
            typeMap_.Add(type, i);
        }
        return i;
    }

#endregion Internal

#region Interface

    public bool TryRead<T>(IVec2 id, out T value) => pools_[id.x].TryRead<T>(id.y, out value);

    public bool TryWrite<T>(IVec2 id, in T value) => pools_[id.x].TryWrite<T>(id.y, in value);

    //...

    public IVec2 Add<T>(in T value)
    {
        var pIdx = EnsurePool(value.GetType());
        var idx = pools_[pIdx].Add(in T value);
        return new(pIdx, idx);
    }

    public TPool GetPool<TPool>(int idx) => (TPool)pools[idx]; // If CreatePool is overloaded, this can be used to access extended functionality added to pools

#endregion Interface

#region Fields
    Dictionary<Type, int> typeMap_ = new();
    List<IPool> pools_ = new();
#endregion Fields
}

//...
//Usage in scheduler

public interface IJob
{
    void Invoke();
}

public class Scheduler
{
    private interface IJobPool
    {
        void Invoke(int idx);
    }
    private class JobContainer : TypedList
    {
        private class JobPool<T> : Pool<T>, IJobPool
            where T : IJob
        {
            public void Invoke(int idx)
            {
                if (TryRead(idx, out T job)) // Accessed and invoked without ever lifting to the heap
                {
                    job.Invoke();
                }
            }
        }

        private override IPool CreatePool(Type type)
            => typeof(JobPool<>).MakeGenericType(type).GetConstructor(Type.EmptyTypes).Invoke(null); // Overridden to add Invoke
    }

#region Internal
    private void DoJob(long id)
    {
        IVec2 jobId = GetJobInfo(id).jobId;
        jobs_.GetPool<IJobPool>(jobId.x).Invoke(jobId.y); // Scheduler doesn't need to know type of job to execute it
    }

    private IVec2 StoreJob<T>(in T job) // If the scheduler is running any kind of loop (it probably is), job storage will be reused, thus avoiding basically any gc allocations
        where T : IJob
    {
        return jobs_.Add(in job);
    }
#endregion Internal

    //...

#region Fields
    JobContainer jobs_ = new();
#endregion Fields
}
It's a pretty broadly applicable pattern if you're trying to avoid GC costs. That said I'm almost certain that using type params with virtual/interface methods has a hidden cost, but whatever, it's either that or lifting to the heap.


God do I wish C# had C++ style templating 😩 So many cool things require weird esoteric reflection that honestly I'd rather do with weird esoteric templating. Am I a sick person for feeling that? Maybe.
 

Null

Ooperator
kiwifarms.net
2021-07-22_22-31.png

>tfw you spend all day writing code and you have nothing to show for it but a few console lines.

Basically, my simulation is too complicated for its own good and the only way to make it efficient is to parallel thread it and to do that you have to learn and abide by a few programming practices which are remarkably different than anything I know and also very hard to understand, conceptualize, and implement correctly.

My grid might by 256^2 with a depth of 3 so that's about 196,608 'Orts' (I needed a word for 'bit of air' and someone suggested the german word for 'Spot'). Unlike most voxel games, this does not represent the ground but instead the air. An Ort is a cubic meter or kiloliter of air, but as those are both units and I need a name, I went with Ort.

The question is, if you have 196,608 Orts and then you have a bunch of chemicals that can be in the air, how do you efficiently handle that? Well, you don't, probably. A lot of the issue with parallelism and something at this scale is you have to be able to presuppose a lot. The more abstraction (classes / entities) you have, the worse it gets. The more dynamic things like arrays are, the worse it gets.

If I want to represent the chemical composition of an Ort, I need another struct that tells me which chemical it has, how much of it, and at what temperature. Well, I probably have more than one (air by itself is Oxygen and Nitrogen so having more than one is the default, not the exception) so I need to be able to accommodate between zero and the total number of simulated chemicals in the game per Ort.

The Chemical is a struct that is about 64 bytes in size. That's a big boy. So I have to somehow keep track of 196.608 Orts that each have 64 bytes of data attached to them n times. So for just a 256 grid with breathing air, that's 25MB of memory which has to be managed. Managing this at a high level with garbage collection is going to be very slow, so I'm writing pretty low level code and I'm going to have to implement custom mesh generation because without the entity system Unity's hybrid renderer can't make meshes for me.


To solve a lot of my problems, I've started relying heavily on what I call Mortons: the serialized value of a coordinate. I've moved a lot of shit to BlobArrays. These are immutable data sets which are single-dimensioned, but can be accessed serially and, as a benefit, can be read parallel - so putting Chemical data in one does not stop 196,608 Orts from reading it together.

Problem: You cannot store a reference to a BlobArray in another BlobArray, and you cannot edit them. So the majority of data - the actual Chemical compositions that exist in the Orts - cannot be in one.
Answer: Native Streams.

Old struct (buffer elements which belong to entities)
1626986847038.png


New native streams (arrays in a system)
1626986830736.png


The native streams are built out knowing 1) how big the grid is, 2) how many chemicals are in the simulation. Say you have a 3x3 grid that's 3 Orts tall (27 Orts) and you have [0]Nitrogen, [1]Oxygen, and [2]Chlorine Gas. I populate the grid, create and store the chemicals, and then pass that data to the Ort system. Those two native streams should look like this:

Matter
[0] 11.472f
[1] 4.704f
[2] 0f
[3] 11.472f
...
[79] 0f
[80] 0f


Heat
[0] 293.15f
[1] 293.15f
[2] 0f
[3] 293.15f
...
[79] 293.15f
[80] 0f


This is the same data as having 26 of the structs, but this can be accessed in parallel and with as little data as possible.

One thing the struct contains that the array does not is a reference to the chemical itself. This is not needed because the chemical id can be obtained deterministically from the index of the array.

ort count = 27, chemical count = 3, so the arrays are 81 items long (0 index).
ort = i / 3
chemical = i % 27

Thus, the 5 bytes per chemical composition to identify the chemical have been completely eliminated and now the data can be ripped open on a 64 core system without issue.

Tomorrow, diffusion!

Actually, I stream tomorrow. SATURDAY!
 
Top