10 Reasons NOT to use Unity as your game engine

Unity3D has undoubtedly been a popular game engine choice for many developers, especially indie developers, due to its ease of use, cross-platform capabilities, and extensive asset store. However, like any software, it has its drawbacks that may make it less suitable for certain projects or teams. In this blog post, we’ll delve into the ten worst aspects of Unity3D, shedding light on some of the frustrations and limitations that users may encounter.

  1. Import, reimport, reimport, reimport, reimport: One of the most frustrating aspects of Unity is the constant need to reimport assets. Whether it’s textures, models, or scripts, making changes often requires multiple reimports, leading to inefficiencies and delays in the development process.
  2. Way behind Unreal on critical technology (Nanites): Unity has been criticized for being slower to adopt cutting-edge technologies, such as Unreal Engine’s Nanite virtualized geometry, which allows for incredibly detailed environments without performance loss. This can be a significant disadvantage for developers looking to create visually stunning and optimized games.
  3. Fragmented feature sets among different render pipelines: Unity’s three main render pipelines – Built-in Render Pipeline (BRP), Universal Render Pipeline (URP), and High Definition Render Pipeline (HDRP) – have fragmented feature sets. This creates confusion for developers who may need to switch pipelines or encounter limitations that hinder their vision.
  4. Fragmented documentation among different render pipelines AND different versions: Documentation in Unity can be a mixed bag, particularly when it comes to render pipelines and different Unity versions. Often, documentation can be outdated, incomplete, or confusing, making it challenging for developers to find accurate information and troubleshoot problems effectively.
  5. Platform inconsistencies: Developers often face issues where features that work flawlessly in the Unity editor do not function as expected on the target platform. Even for popular platforms like Windows, problems may persist, such as struggles with HDRP Area lights that fail to work correctly.
  6. Integration with popular modeling software is never seamless (Blender): Integrating Unity with popular 3D modeling software like Blender can be a cumbersome process. Issues with importing, exporting, and maintaining proper object hierarchies can lead to time-consuming workarounds and hinder a smooth development pipeline.
  7. Team Development is hard (Meta files, scene merges): Unity relies on meta files to store crucial information about assets, which can create conflicts during team development. Merging scenes and handling version control can be difficult and error-prone, leading to wasted time and effort.
  8. Long compile times: Even for relatively simple projects, Unity’s compile times can become painfully long, causing productivity setbacks and frustrations for developers who need rapid iteration.
  9. Long bake times that often don’t even work: The baking process in Unity, used for pre-calculating lighting, can be time-consuming, and sometimes the results may not be as expected. Frequent issues with lighting artifacts and discrepancies may arise, adding to the frustration during development.
  10. Expensive, yet updates aren’t coming fast enough: Unity’s pricing structure may not be as budget-friendly for small indie developers. However, some users argue that despite the cost, the engine’s updates and improvements often lag behind expectations, making it difficult for developers to stay up-to-date with the latest technologies and features.

While Unity3D remains a popular choice for many game developers, it’s essential to acknowledge its limitations and drawbacks. From the frustrating import process and fragmented documentation to platform inconsistencies and long compile times, Unity has its fair share of issues that can hinder development productivity. Despite these downsides, it’s essential to remember that no game engine is perfect, and developers must carefully evaluate their project requirements and team capabilities before choosing the best engine for their game development journey.

Install GPTJ on WSL

  1. open cmd as administrator
  2. wsl –install
  3. reboot
  4. wsl
  5. install CUDA toolkit
  6. wget https://repo.anaconda.com/archive/Anaconda3-2022.10-Linux-x86_64.sh
  7. ./Anaconda3-2022.10-Linux-x86_64.sh
  8. Reboot entire machine AGAIN
  9. mkdir gptj
  10. cd gptj
  11. conda create -n gptj python=3.8
  12. conda activate gptj
  13. conda install pytorch torchvision torchaudio pytorch-cuda=11.6 -c pytorch -c nvidia
  14. pip uninstall -y transformers && pip install --no-cache-dir https://github.com/deniskamazur/transformers/archive/gpt-j-8bit.zip
  15. pip install bitsandbytes-cuda111
  16. pip install datasets==1.16.1
  17. pip install torch==1.11.0+cu115 torchvision==0.12.0+cu115 -f https://download.pytorch.org/whl/torch_stable.html
import torch
import transformers
from transformers.models.gptj import GPTJForCausalLM

device = "cuda" if torch.cuda.is_available() else "cpu"
tokenizer = transformers.AutoTokenizer.from_pretrained("EleutherAI/gpt-j-6b")
gpt = GPTJForCausalLM.from_pretrained(
    "hivemind/gpt-j-6B-8bit", low_cpu_mem_usage=True
raw_text = open("prompts/delandzombie.txt", "r").read()
text = raw_text
prompt = tokenizer((raw_text), return_tensors="pt")
prompt = {key: value.to(device) for key, value in prompt.items()}
out = gpt.generate(
out = tokenizer.decode(out[0])
text = out
raw_text += text
output = open("out.txt", "a")
    + "\n"
    + "\n"
    + "------"
    + "\n"
    + "\n"

Why I chose the Kaabo Mantis King GT as my e-Scooter

When it comes to e-scooters, the market is full of options, making it challenging to choose the right one. But after careful consideration, I decided to invest in the Mantis King GT. There are several reasons why I chose this particular model, and in this blog post, I’ll share my thoughts on what makes it stand out from the competition.

Continue reading “Why I chose the Kaabo Mantis King GT as my e-Scooter”

Forget try..finally! New Approaches to Garbage collection in Delphi/Object Pascal with help from the commonx library.

I’m not very good at communication, so it may seem like I don’t contribute to the community much. Like many of you, I’m pretty dang busy most of the time. I have made a pretty big contribution to the Delphi community available on GitHub, although most of you probably have simply never downloaded it, because I’m pretty bad at communicating what it actually does.

If you want a giant set of reusable useful, albeit poorly documented, classes for Delphi. Go to GitHub and check out adaloveless/commonx. This repository is a Batman toolbelt of everything and anything I find useful. I do MOST of my work in this library, as I’m pretty obsessed with making everything reusable and portable.

Getting into commonx is a deep rabbit hole, so I figure I’ll just introduce it by starting with something really simple and really basic that it can provide:

Alternative Garbage Collection Techniques for Delphi

Typical Delphi usage of a class involves some very repetitive tasks that the broader language-agnostic programming community has evolved away from. Typically you would use a class in a pattern similar as follows.

var MyThing := TSomeObject.create;try

In languages with garbage collection…. all of this could be compressed into just a single line….

Here, 6 lines of code becomes just 1! The creation of TSomeObject would return a reference that could be immediately used and then automatically freed. This kind of Automatic Reference Counting (ARC) system was met with much resistance because of all the legacy code (and legacy coders) out there that were simply not used to it. Also it didn’t help that Embarcadero made every single platform they targeted use a different set of rules, infuriating just about everyone. (They further fueled the fire by being super indecisive about whether strings should be 0 or 1 based… following different rules on every platform.)

ARC was subsequently scrapped on ALL platforms… and I must admit I like that it is consistent now… however, I always wondered… why can’t I have ARC optionally? It is convenient for lots of cases, and avoiding it is only necessary in the most performance sensitive of situations.

I then sought out to come up with a way to implement ARC-like functionality for the purposes of avoiding memory management nightmares and try..finally ugliness all over the place and aimed to package it in the simplest way I possibly could.

I came up with a few interesting mechanisms that I will describe in this article. THolder<>, which implements a “smart pointer” in Delphi. TAutoScope, which cleans up anything you put into it when it goes out of scope… and for an added bonus, I’ll talk about TGiverOf<> which implements a simple-to-use object pool.

To gain access to commonx, get it from GitHub and set a reference to it in your project settings. Start a new app and include the path to “commonx” in your search path. If you’re building for VCL also include “commonx\vcl” in your search path. FMX project should include “commonx\fmx“.

Take a look at betterobject.pas

Interfaces in Delphi are reference-counted with ARC. So a simple solution I thought would be to simply come up with a generic interface that could hold onto any kind of object and I came up with IHolder<> and THolder<>. THolder<> can be assigned to it’s counterpart IHolder<> without casting. Rewriting the aforementioned code snippet to use THolder results in code that looks like this.

var MyThing : IHolder<TSomeObject> :=    THolder<TSomeObject>.create(TSomeObject.create);MyThing.o.DoSomething();

You could get it down to one line with a simple cast, but I’ve never tested this and it’s not really a goal to have it down to 1 line.


The real magic here is that try .. finally is not required and you have to worry less about leaked objects that you forgot to free. The only thing I don’t really like about this is that you have to dereference the property “o” which contains the actual object you’ve wrapped. But I’ve gotten used to doing this in ALL my projects now and it is a big time saver.

If you don’t want to dereference “o” each time, you really only have to dereference it once. You may choose to do to reduce the number of dereferences… speeding up your code… or simply making it less-ugly. Type inference is really a godsend here.

var MyThingH : IHolder<TSomeObject> :=
var MyThing := MyThingH.o;

THolder/IHolder furthermore solves those situations where a class is returned from a function, but nobody is sure who is responsible for freeing it. For example I have code in my projects that returns a JSON document, but that JSON document might also be stored in a cache. It would crash the program if I destroyed an object from the cache while some thread was using it somewhere… so a reference count would have to be maintained… destroying the documents only when all the references were removed…. IHolder<> comes to the rescue!

function GetJSONDocumentCached(url: string): IHolder<TJSON>;
  result := GetFromCache(url);
  if result=nil then
   result := PutInCache(GetFromWeb(url));
var jsonHolder := GetJSONDocumentCached('whateverurl');

Somewhere else in the code there could be a simple TArray<IHolder<TJSON>> or a TList<IHolder<TJSON>> controlled by a mutex…. (or someday maybe I’ll cover my btree class and show you how you could declare TBTree<IHolder<TJSON>> … there are endless possible time savings that you can dream of.

Using this pattern, I never have to worry about managing the lifetimes of my JSON documents. It can also be used for other classes derived from TObject…. using my TBetterObject as a base is not required.

It also opens up lots of interesting function possibilities that return things like TStringList, like ParseStringH() in stringx.pas which returns a IHolder<TStringList>… so there’s no need for a separate call to Free()!


Let’s say that you already have a bunch of code out there… and maybe it’s some ugly bad code that someone else wrote into a 40,000 line function that you just want to quickly mod without indenting 40,000 lines of code with a new try..finally… and screwing up your SVN merge. Enter TAutoScope… or really IAutoScope.

I’ve conveniently provided a single function, simply called AutoScope() in betterobject.pas that allows for you to instantiate a garbage collector for any scope you want. When the scope ends so does the AutoScope and every object you tell it to track. It looks a little something like this:

var someObjectIDontWantToFreeLater := TAnything.create();
var Scope := AutoScope;
Scope .Track(someObjectIDontWantToFreeLater);
end; //<--AutoScope and everything Track()ed are freed here

IAutoScope and IHolder<> can be placed in any scope…. so they can be attached to inline vars inside begin..end, at the function level, global, inside a class, a record, or even inside an array or even an array of records! Free() will be called automatically every time the object is no longer being used!


TGiver and it’s generified, more useful variant, TGiverOf<>, implements object pooling for any kind of object. To use it, simply declare an instance of TGiverOf<TSomeObject>. In my examples, we’ll imagine we’re sharing network connections, because that’s often useful.

var //global
  ConnectionPool: TGiverOf<TMyConnectionClass>;
  ConnectionPool := TGiverOf<TMyConnectionClass>.create();

One nice thing about TGiverOf<> is that it understands IHolder…. so it will return holders to objects simplifying the how and when the objects are returned to the pool.

var connection_holder := ConnectionPool.Need();

If the type inference is confusing you in the above example, here it is expanded:

var connection_holder: IHolder<TMyConnectionClass> := ConnectionPool.Need();

You can limit the number of objects in the pool with the Limit property.

ConnectionPool.Limit := 10;

You can also override GivenIsGivable() to setup rules for cleaning up the pool when objects expire, although upon reading this code I am thinking I should extend it with an anonymous method property. I typically manage the pool by overriding ShouldGive() and ShouldReturn() in TBetterObject… but that would only apply if you’re using GiverOf to serve up TBetterObject variants (not always the case,but the most typical scenario for me.)

Anyway… I’m all up for ideas and suggestions. If there’s anything you’d like to see in these classes, fire me a comment, and I’ll see what I can do! Or better yet… become a contributor to the GitHub project!

Fix Windows 11’s Most-Annoying “Feature” in just a Couple of Clicks

Window’s 11’s Context Menus… flat-out SUCK. These are the menus that come up whenever you RIGHT-click on many things. Power Users often install things that inject new shortcuts into the context menus that are very useful… however, in Windows 11, those menus now get buried, requiring extract clicks to access them…. it’s really a hinderance to productivity, because usually the things that you want to access in those menus are those things that you use extremely often. As a programmer, I use apps like Tortoise to sync up my code repositories and compare code merges, among other things… so to go from 2-clicks to 3 clicks to find my the option to commit my files to the repository, is suuuuuper annoying.

Luckily… if you want the old menus back…. just run cmd.exe and type the following.

reg.exe add "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32" /f /ve

Once the command is run, reboot or logout and back in.

If you want to get the old menus back (You like being tortured and insulted). Do this to reverse it.

reg.exe delete "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}" /f

How Copy a MySQL Database

Wouldn’t it be nice if it was as simple as “copy database1 to database2”? Or something like that? But that would be too easy! Although to be fair, copying a MySQL database is easier than many other databases. But the caveat is that if you do it wrong, it is easy to accidentally export the database names, and importing an improperly exported database potentially overwrite production data if you’re not careful.

Continue reading “How Copy a MySQL Database”

Dusting off A 20-Year-Old Delphi Documentation Generator

Proving that some Delphi code can be parsed by my 20-year-old doc generator.

Years ago I was told by an asshole boss at a company I worked for that I was going to be “fired” if I didn’t “document my code” (although it should be noted that I maintained a department library of dozens and dozens of white papers on our software design… but he was too much of an asshole to care).

To shut him up, I showed up to the scheduled meeting a month later with a stack of paper as tall as two dictionaries, full of detailed information about every single class, method, and function written by our six-person development team. I determined from the start that there would have been no way I could have done this manually, so I spent weeks of my personal time building a documentation generator to handle the chore with its own Pascal language compiler.

Although barely working, I have brought my old documentation generator back to life and built a barely-working barebones web front end for it online for the purpose of documenting my Delphi CommonX library, but don’t get too eager to read much of it because the documentation sitting on the documentation server is super sparse and simply for the purpose of testing a new searchable database of classes and symbols.

Continue reading “Dusting off A 20-Year-Old Delphi Documentation Generator”

After Nearly a Week of Pissing Around, I Finally Got Delphi to Display a Downloaded, Local PDF on iOS When Sandboxed

Computer programs have become more complex and connected to the world via “always-on” connections…. and due to the constant threat of new internet exploits, Operating systems are a frustrating, moving target.

As a result, certain things that worked on the last version of an operating system are banned in the next, and the churn is often miserable, especially if you’re a small company with limited resources. Simply keeping your app available on the Apple App Store requires regular maintenance, periodic updates, and frustrating amounts of your time.

I finally got my client’s PDF Reporting functionality restored in their iOS app and the solution was very simple, although it required trial and error. In fairness, the solution for iOS was much simpler than the Android solution that I had to figure out a few months back. If you need help with Android, fire me a comment and I’ll possibly update this blog.

Continue reading “After Nearly a Week of Pissing Around, I Finally Got Delphi to Display a Downloaded, Local PDF on iOS When Sandboxed”

The DUMBEST Products that Apparently Sell Like Hotcakes on the interwebs — A Continuously Curated List

I tapped into one of the internet’s most notorious motivators of SPAM ads, Clickbank, to find you the hottest-selling trash I could find. Using ClickBank’s own statistics engine, I went through all of the top sellers, to figure out what kind of crap people are pushing and why.

Clickbank is an online marketplace where the people who want to get their products out there set the terms that they are willing to pay for a sale, and the sky is the limit in terms of what companies may be willing to pay. Sometimes they are allowing for recurring revenue through subscriptions to be passed on to the sellers, creating extra motivation, driven by subscription revenue. They pay up to 70% commission to anyone who can bring customers through whatever means necessary, which can include spamming your email, spamming Facebook groups, and “influencing”. Motivated sellers foam at the mouth and set up all kinds of sleazy methods to sell more of these products than the next guy.

ClickBank offers potential sellers the opportunity to look at the average earnings per sale as well as the “gravity” (essentially popularity) of the product. Products with high gravity are actually selling out there… here’s some of the crap that is doing well.

Continue reading “The DUMBEST Products that Apparently Sell Like Hotcakes on the interwebs — A Continuously Curated List”