Friday, November 1, 2024

Last Epoch is a really good Diablo-3 / POE mashup

If four years from now Diablo becomes a dead franchise, Last Epoch (steam store link) will be the game that stole all those players. 

This game is doing to Path of Exile what Blizzard-WoW did to Everquest.. taking the kernel of the best ideas and making it pretty and accessible for the rest of us.

If you want spoiler free, stop here, go buy it on steam, and try it out.

If you want some (minor) spoilers, read on....

Last Epoch has the "lite" versions of POE mechanics like class-ascendancy, mapping, and gear-reforging, and then added a mountain of quality-of-life that makes everything just feel *so* good as I play more.... My favorites are gear-modding *anywhere* (literally anywhere, you just open a menu), and an in-game loot-filter configuration UI! 

The new-player-experience has a bit of clunkiness to it, and the story and cutscene production value is pretty darn low, so it took me 2-4 hours to "get into it", but once I did, I got hooked. Their class/skill system is just such a unique blend of ideas from D3/D4/POE, and is such a neat balance between them. 

The monsters and world feel is very much like D3 (which is a great thing). 

The classes are like Path of Exile, where there is a base-type, and then just a short way through the campaign, you pick from three class-subtypes (which can not be changed!). This selection has a *much* bigger effect on skills and skill trees than it does in POE. (in POE it dictates ~12 ascendancy points, but in LE it controls the entire second half of the passive-skill-tree and what remaining spells you can choose from)




The skill and passive-tree systems are vaguely D3/D4 class-specific structures, with very *easy* respecing (though somst costs, especially for changing spells) -- I'm usually for D3 style free respeccing, but I also can see the value in there being *some* cost to respeccing, as it creates more consequence for choices. 

Which brings me to the the best part, the gearing and gear-upgrading system.... Last Epoch took a page out of minecraft, fortnite and "you're the crafter" model, and lets you modify or "disenchant" your gear anytime, anywhere. You are your own personal gear enchanting system, and the way it feels in the game is amazing, because...



In-game Loot-Filter! There is a really big gap from the Diablo world of no-loot-filter mayhem, and the Path of Exile world where loot-filters are created with outside software and imported into the game....


Enter Last Epoch, which has a really simple in-game UI for creating a loot-filter. It's not nearly as rich as what POE can do, but the in-game feel of being able to push a key, and make a loot filter spec at any time, is actually really great. This feels like the kind of thing Blizzard usually does to the competitors.. takes their ideas and just makes them accessible and great, but in this case, it's in a quasi-indie game.

There are some things I don't love... probably the biggest of which is that there are some unique-affix-bonuses on gear that are so powerful and pivotal, you get stuck with a crap piece of gear because you can't afford to lose that unique bonus (see highlighted bonus on the right). 

I think the D4's aspect system was an attempt to fix this (which is
pretty good in S6), but then they have unique gear with affixes you can't aspect-craft, and then you're stuck again, wearing that level 8 glove until level 60. However, this entire category of ARPGs has this problem, so it exists unless you go further away from the Diablo-subgenre into games like Warframe or VRising.

Every ARPG has moved to a quasi-seasonal model now, with content releases once or twice a year, and Last-Epoch is following this model. They've only release a few patches so far, so it's too early to tell how good this will go. Path of Exile sets the bar on this, so we'll see.

If you have some time to kill, give it a try.

Here are some excellent overview videos:



Tuesday, December 19, 2023

Is California AG Rob Bonta in Apple's pocket?

Today I read this article... 

California AG blasts ‘greedy corporations’ in $700M settlement with Google - POLITICO

"More significantly, Bonta said, the settlement requires Google to change its practices, including by allowing Android users to install apps on their devices from third-party stores outside the Google Play Store."

Dear Mr Bonta,

Google Android has allowed third-party app installs and app-stores since 2008, and many many handsets, including the popular Samsung Galaxy handsets, come with third party hardware vendor app stores pre-installed. Amazon Appstore for android was shipped in March 2011. 

Meanwhile, Apple not only disallows third-party app stores, but also disallows third party SMS messenger clients, third party web-browser-engines, and has closed iMessage and Facetime networks. 

I think you are going after the wrong company if you think Android is anti-competitive.

Please set your sights on Apple.

The fact that you have not already makes me wonder why.


Saturday, April 22, 2023

Dart... a Typescript-ish with AOT compilation but a clunky Javascript interface.

I've recently been looking into Dart, originally released in 2011, trying to understand why it exists, and whether it's worth using in 2023.

TL;DR

Consider these questions...
  • Do you want to write a single UI for mobile and web in Flutter?
  • Is iOS execution speed more important to you than using community modules?
  • Are you serverless (e.g. Firebase) or happy writing a non-Dart server?
If you answered "YES" to all of these, then Dart/Flutter may be worth a look. If not, I'd say take a look  at TypeScript instead. Main reason being, Dart's native-compilation strategy, which is why can run faster on non-JIT platforms like iOS, also makes importing Javascript modules into a Dart project a major interop marshalling mess, because JS can't run on Dart Native, and there isn't (yet) a rich ecosystem of Dart modules for either client or server like there is for Typescript and Javascript.

Dart History

In 2010, the Google-Oracle-Java lawsuit started, and shortly after in 2011, Dart was born. I was no longer at Google at the time, having left late 2009, however, from the external press about Dart, it sounded like Google was hedging an altnative Android development language should the lawsuit create problems for the Java-Android-Dalvik ecosystem. The result was Dart and Dart-Native, and an early version of Flutter. The Supreme Court ultimately decided Google's use of the Java APIs to be fair use, saving our industry from mass chaos, and also making a cumbersome transition of Android developers away from Java to Dart not a priority.

Previous to the lawsuit, there had been increasing work on languages transpiling to Javascript, because of the unweildy nature of large front-end codebases in the dynamic-typedprototype-basedcallback-handler heavy Javascript code style. In 2006, Google had experiemtened with Java-to-Javascript in GWT, with little success. Other transpilation languages targetted code-cleanliness more than static-type checking, such as CoffeeScript (2009) and ClojureScript (2011). While few knew it, in 2006 a little known statically-typed language called Haxe started transpiling to Javascript.

In 2012, Microsoft employed language phenom Anders Hejlsberg released TypeScript, which if you squint a bit looks like Haxe, developed by Microsoft 6 years later, but with lots of robust compiler services and a big company behind it. 

In 2013, Google got involved, announcing the Dart-to-JS compiler, which had a different long term vision of pushing browser-runtimes towards the DartVM rather than the other way around. Shortly after, Google proposed AtScript (2014), a TypeScript superset to handle some things Angular needed. When AtScript concepts were merged into TypeScript 1.5, both Angular and React, the two most popular web-frameworks, were standardized on TypeScript, and it effectively "won" typed webdev, again eliminating the niche Dart was hunting for.

And so what is Dart for at this point? Well, there is one nagging little thing that TypeScript/V8 is pretty bad at doing, which is running efficient compiled native code on mobile platforms, such as iOS, which doesn't allow applications to JIT.

What is Dart?

Dart is a statically typed language, more like Java or C# than TypeScript, which is optionally typed. As a result, Dart can be ahead-of-time compiled down to semi-quick native code.

Dart's design uniquely satisfies two tricky competing goals. The first is to be behaviorally like Javascript, so it transpiles to efficient Javascript. It is far from the only language that does this. However, it's next trick is more interesting, the Dart Native ahead-of-time compiler can pre-compile code to run outside a browser. This eliminates the JIT overhead, and on platforms like iOS that forbid JIT and writing to executable code pages, it offers substantially better efficiency than mobile Javascript runtimes (The V8 javascript runtime "no-JIT" mode is an Interpreter on iOS).

Tradeoffs in Efficient Javascript Transpiling

In order to make idiomatic Dart compile to Efficient Javascript, Dart has to remain close to Javascript in behavior. One of the most significant elements of this, is that Dart is a completely reference-type language, like Javascript.

One of the tradeoffs this creates, is that in order to get efficient packed-arrays-of-anything, one has to use the Javascript ArrayBuffer/DataView library APIs. This has overhead and limitations compared to C# parametric instantiated value-types. First, because every access is trampolined through the accessor functions, which hopefully the JIT compiles out, and two, DataViews can not contain object references like C# structs can.

To understand this issue of transpiling to efficient Javascript, one can imagine the opposite compromise, transpiling C# to Javascript. In C#, structs are value types, something as simple as struct Vector2D {float x,y} in C# is *copied* each time it's passed in a function call or assigned to a storage location - this is very fast, and is always faster than using references for structs up to a pretty surprising size. Structs being copied means code can rely on them being immutable (you can only mutate your local stack copy). 

If one wants to transpile an idiomatic C# value type struct into Javascript, some tricky compromises insue. 

Immutability. In theory, if one controlled all code, we could redefine a new "immutable" property of a reference, and track that around our generated code, assuring that generated code would create a copy before any mutation. However, if any non-generated code ever gets ahold of that reference, it could change it, thus violating our code assumption of value-type-immutability. A safer approach is to copy the Vector2D every time it's handed to a function, to assure immutability. However, this has lots of overhead every time a function is called, when the Vector2D is unlikely to ever be written to. 

Equivalence. After we solve this immutability issue, we run into the equivalence issue. Two C# structs are equal if their entire contents are equal, which can be overridden with an Equals() override.  However, two Javascript objects are only equal if their references are equal. They can have the same contents, and be not-equal. While one might be tempted create some form of comparison operator on a javascript object, once structs nest objects or objects nest structs, and these things get handed outside of generated code, enforcing the C# behavior expectations get messy fast. One alternative would be to use ArrayBuffer for every C# struct, but this will make the Javascript slower, not idiomatic Javascript, and incapable of storing references. 

These are just two examples, but these types of impedence mismatches are why it's difficult to make a language with value type expectations efficiently compile to Javascript. 

And so Dart, and Typescript, and Haxe -- as languages which want to efficiently compile to Javascript -- do not have native language value types.

Dart Javascript Interop Mess

The baby that got thrown out with the bathwater in Dart's competing goals of efficient compilation both to Javascript and to native-code, is that interop with Javascript is a total mess.

This is because that statically tyepd Dart code *may* be running in compiled form, and Javascript can't be compiled to static native code, because it's not statically typed. Thus, to include Javascript into a Dart project, Dart assumes that Javascript might actually be running in a completely separate interpreter. Imagine your Dart code compiled to C, which embeds V8 to run your Javascript include. Because that might actually be happening when Dart is compiled to Native.

This blows away the trans

Dart for the Web

When just considering the code-syntax Dart (really Dart 2) is a bit like Typescript, but with stricter static typing, and some differences to enable non-web targets. 

One of those big differences is that one can not seamlessly intermix Javascript and Dart, the way one can with Typescript. In Typescript, types are optional, and every valid Javascript file is a valid Typescript file. This is because Typescript always compiles to Javascript. 

Even though Dart *can* run inside a browser as transpiled Javascript, this is not the *only* way it can run. As a result, Dart can't seamlessly interop with Javascript. If you include a Javascript module, when your Dart code gets native compiled on iOS, for example, a separate Javascript runtime will handle the Javascript code, which means they don't share the same heap and data, and any data moving between Dart and Javascript will have to be marshaled. Marshaling is expensive. In short, you will be better off *converting* code you'd like to leverage into Dart, than trying to 

Dart also has some minor differences, like a slightly different import namespace, to help code accomodate web and native targets togerther. And Dart has a really nifty method-cascade operator (..), to call many methods on the same object without repeating the object-varaible or having to do small-talk style "return this" methods. Hopefully more languages will adopt this in the future. 

Dart Native Ahead of Time Compilation

A huge difference between Dart and Typescript on non-web targets, is that Dart Native supports Ahead of Time Compilation to native code. This is important for mobile, and iOS in particular, because iOS does not allow writing to executable code pages for security reasons, which forbids the use of runtime JIT. 

When using Typescript and Reactive Native on Cordova, V8 runs on iOS in no-JIT mode, which uses the V8 interpreter, which can be 40-90% slower than native, depending on the workload. So Dart is going to get you faster iOS code than Typescript for sure.

Dart Native, while offering JIT, has a fairly simplistic two-phase Garbage Collector which is not as good as the current V8 Orinoco Garbage Collector, and far inferior to Java ZGC or golang's collector. Fortunately, this isn't an issue, as one can also run Dart on the mature V8 JIT runtime anywhere JIT is allowed... and so I fail to see why anyone would actually use Dart Native JIT.

Flutter vs React 

The real decision to use Dart or Typescript is driven by the decision to use Flutter or React(-Native), because these ecosystems are tightly tied. It's a Dart/Flutter or TypeScript/React world.

While we're not here to talk about Flutter vs React, Flutter's goal is to enable a single app to run across multiple platforms... which is distinctly different than using React and React Native to use a single language (Typescript or Javascript) to write two distinct apps for mobile and web.

Conclusion

Should Dart exist? Is it worth learning?

Dart was born as a hedge for Google to have independence from Java during the Google-Oracle-Java lawsuit, and has shifted focus over the years to find a useful niche. Today, it offers a Typescript-like language with the unique capability to do ahead-of-time native compilation for iOS and Android while still supporting the fast and mature V8 runtime. 

However, this unearths a rather annoying and unique Dart wrinkle known as JS-Interop. Because Dart Native can't *run* javascript, interop with Javascript libraries must assume any javascript code is 

However, the use of ramework choice, because one has to use Dart/Flutter instead of TypeScript/React(-Native), there is no practical option today to use React with Dart. Flutter's goal is to write one single app codebase that flexibly runs across platforms (web, mobile, desktop), much like Java Swing back in the 1990s, instead of TS/React's goal to use one programming language to write 2-3 separate apps (mobile, web / desktop). 

(see Flutter App Development Cost: Some Things You Should Know | ProCoders)



One big advantage of Dart is being able to use Flutter's more cross-platform focused design. For small teams, or very fast bringup, one can lean less, use fewer tools, and write less code to get running mobile/web/desktop apps with Dart/Flutter than by combining Typescript, React, ReactNative, Cordova, and Electron into a similar ecosystem where one still has to write at least two different distinct apps -- or even more work writing custom Swift/iOS and Java/Android apps.

However, Dart/Flutter comes with some disadvantages as well. From anecdotal experience and word-on-the-street, the Dart compiler and ecosystem are less mature than Typescript and React, and so one is more likely to run into compiler bugs and clunkiness (though I wouldn't rule it out in either ecosystem). 

Further, while it's possible to write a simple server-side in Dart running on Node.JS, and Dart can call into Javascript modules, "nobody is really doing Dart on the server". The Dart-to-JS Interop system is more complex than Typescript, and so the experience is going to be much more manual and clunky than the mainstream TypeScript/Node experience, where one installs javascript modules and typescript type defintionions and is off to the races. Therefore, for more than experimentation, give up on the dream of a single codebase, and expect to write the server in something non-Dart, such as Typescript, Java, Golang, or Python.

Further Reading