Jonathan Blow does a lot of good interviews, I'm sure he'd love to be on here actually. He did some great interviews with No Boilerplate, and No Frauds Club.
In that talk I get the impression that he actually respects rust but that it is not the right tool for for him or game design in general. I also get the impression that while he is an opinionated man willing to express those opinions that he is not mean and he is happy to hear out/encourage other opinions without belittling them.
Your impression resonates with me I think anyone serious about making a language has to understand and respect what other language designers did. But on the other hand being opinionated helps since basically everything you decide for a language is an opinion you force onto other. So forming opinions and being informed of opinions are required skills for the job.
I'm a regular on his streams for years now and somewhat disagree. If he wants to, he can be nuanced and say his opinion in a way that is very hard to fault, but most of the time, he doesn't want to. And the outcomes of that make me sad sometimes. If someone comes to the stream, asks a random question, and gets absolutely trashed for it, that's not good. He can be mean.
@@simivb Certainly, he's no Carl Sagan. But in the Witness, he featured James Burke and other decent science communicators (like astronauts) and I think it illustrates that he may want to be approachable, but just have limited capacity/patience to practice it. I'm giving him the benefit of doubt, but it's certainly a shame, given Jai has a lot a potential - at this point I expect it to be in closed beta until some entity creates a superior publicly available one.
I've listened to a lot of JBlow's stuff, and yes, he does respect Rust. In some of his earliest lectures on what he wants from a programming language (now Jai) he mentions it several times. He likes that it is trying to have a strong and unique opinion about how programming could, or should, work. A lot of modern languages are just syntactical sugar or supersets of pre-existing languages, with very few, if any, truly innovative ideas. He respects that Rust is doing something different. As for opinions, well he can definitely be mean/abrasive when he's in a bad mood lol. He often parrots a gate-keeping mentality and in my opinion, it doesn't do him any favors. I think its a bit of a shame because he is an absolutely brilliant mind, and I can highly recommend watching any of his talks if you're into game design/development.
For clarity, when Jon Blow talks about "entities", he's not referencing to any sort of ECS mechanism like 98% of the time. The term "entity" is as generic of a term as the "variable" in his working style. They are just a bunch of collection of data that represent things that can exist in a game world. That's all. Don't think too hard.
Another way of saying the same thing: when a Java developer talks about an object, they're not talking about a thing whose (narrowest) type is `java.lang.Object`, but rather... any instance of any class. Entity is similarly general, although along a slightly different axis: it's any obj... uh... _thing_ which exists in the game world. A player, a monster, a tree, a wall, a vehicle, whatever.
Entities is another word for an object, but like an object in the real world like a cup or something. Its a thing that can Normally be interacted with someway in the game world. Its really really annoying that programmers steal random words for random shit. Wish we could just use the word object, but that means a bunch of very different things now.
the scope of the generality is misunderstood by people - the monster is an entity, the tile the monster is in is also an entity, one is in a queue, the other in an array .. an entity is much more general that "object"@@jonaskoelker
I think it's the difference between ``` -- init Object with data object = {position=V3(1,2,3), health=42, maxHealth=50, active=true} allObjects[#allObjects+1] = object -- later for _, o in allObjects do if o.active then o.Health = o.maxHealth end end ``` and ``` -- init entity data n = entityCount positions[n] = V3(1,2,3) healths[n] = 42 maxHealths[n] = 50 activeEntities[#activeEntities+1] = n entityCount += 1 -- later for _, eid in activeEntities do healths[eid] = maxHealths[eid] end ``` this allows a direct index to get or set specific-property data by id (number) rather than (object.property) consider the difference: given N objects with M properties each, there will be N * M + N different data-containing-structures. whereas given N entities with M properties, there will be M different data-containing structures. In my experience (significant amount) this is meaningful to bottom-line (what can happen in a frame) when doing simple operations on ~million things 60 times a second. It becomes more important when there are interactions between entities that can be compressed down to operations on data.
7:45 This is what Common Lisp macros are, just Lisp code running at compile time that returns Lisp code. The next benefit they have over other comp time and macro systems is that all Lisp code is already written as Lisp objects, so there's no special operations or types needed to represent code.
This argument for ease of experimentation is why I love to do first drafts in Python, if it turns out that you need more performance, rewrite it in cpp, rust, or whatever systems language. Reciently I had to build an SPI to webrtc bridge to display stats from a DSP in real time, guess what python is slow and there were some bugs I had to solve in aiortc, but in the end it helped me to understand how the whole webrtc signaling process works. Now I'll be rewriting the bridge in rust with a more solid understanding of the process. Most of the time the python implementation is more than sufficient through, which saves me a lot of dev time.
that's the problem though - most of the time you won't rewrite it, so now you have a program in python, with all the problems that go along with that, when you originally wanted a program in cpp, rust, or whatever systems language, and planned to just write a prototype in python. I can virtually guarantee that, while the prototype might be slower to get out if you write it in cpp or whatever, it will take less time overall to write a prototype in cpp and then convert that to a final version in cpp, compared to writing a prototype in python and a final version in cpp.
Regarding the "difficulty of trying stuff out", I remember watching a video where a C++ developer said he used Python for prototyping because it's easier that way. Once he found an solution he liked, he then implemented it in C++.
Can confirm. There was a thing I wanted to write in C, let's pretend it's a Sudoku generator (it's not too far from being concretely true). I had various strategies that partially worked, some of the time, but frequently enough I would generate a puzzle my solver couldn't solve. I used python to prototype various ideas. Failure-and-retry probabilities served as a good enough predictor of performance. Once I got an algorithm I was okay with, I implemented it in C and it worked as expected. Note that the output as a function of input is probably the same in C vs. python, but the performance is not. So be careful about extrapolating; Think about when the prototype is and isn't a good predictor of the characteristics of the production implementation. When is a python prototype the right approach? Intuition says: the longer the exploratory process is, and the more faster you can try ideas out in the prototype language compared to production, and the less time it takes to rewrite. Let's math it out. Say the time cost of implementing x features in language 1 is a_1*x + b_1 and in language 2 it's a_2*x + b_2, the cost of rewriting the final version is r and you need to implement at least t features before your prototype is done and you can begin rewriting. Then the cost of writing in language 1 is a_1*t + b_1; the cost of prototyping first is a_2*t + (b_2 + r). For prototyping to win, you want a_2*t + b_2 + r < a_1*t + b_1, which is equivalent to b_2 + r - b_1 < (a_1 - a_2)*t. This agrees with intuition: the numbers which favor prototyping are greater t (more exploratory process), greater a_1 - a_2 (development time: python beats Scala beats C++), lower rewriting and differential fixed costs. Of course, you can never observe the numbers and so you never know. You can do both, interleaved, and "only" spend twice as much time as the fastest approach. Or you can make a judgement call based on experience.
If you're doing something small or just starting out a project, sure, but if you want to prototype/explore something inside a 1 million line C++ codebase you can't really whip out the python.
@@alexvitkov I generally agree-although one should consider defining some interface around the place where you want to introduce new behavior, which only takes plain data in and gives plain data out. Then you can prototype a python thing which does the right input/output mapping of the data, then rewrite it in your production language once you have the right implementation strategy. This becomes harder if you want to prototype something which reads and/or updates the system state. It's doable: send over all the data you could ever want to read; return a list of descriptions of things to update. Probably requires a lot of elbow grease and the amount of transferred data may be too large to be practical. But sometimes this can work, and hey this technique might be one that works.
Python allowed me to understand and learn more complex concepts a lot faster and easier. I then find out what the equivalent of those are in c++ and understand them a lot better.
I love Jon and I always appreciate his opinions. He's not always right and he's not always making the best case. But he's always concise about how he feels and always brings great ideas into the conversation.
I miss the On The Metal podcast. Every guest had some amazing anedoctes and insights. Even though JBlow's interview was 3 hours long, I didn't want it to end. Every single episode was like that.
Oh god, thats the second reaction to jon blows stuff that contains basically the entire buildup to a point he is making, and then just stops before he makes the point. Im dying out here, what is this
I think a cool case for zig comptime would be parsers. You can get a type safe interface for some file format (think schemas) at compile time without codegen or code first type inference (like zod in typescript)
7:10 I was just actually watching a video on comptime, etc, in Zig, and, coming from C++, Zig has started to feel kind of like that smaller, beautiful language that is supposed to be struggling to get out of C++. I know it's supposed to be like a modern C, but it feels more like what C++ was supposed to be. Like for example, constexpr & consteval gets us in the realm of comptime-- that idea of a first class function -- *not a macro* -- that does compile time calculations to generate some code or some compile time variable. And then we also have the ultra easy interop with C, literally no effort apparently. And defer! Oh gosh, defer looks so nice. It just feels like the same sort of area of why Bjarne made "c with classes". The niceties just shifted from being actual literal builtin classes and polymorphism to all the zig niceties. Ya know? idk. just some thoughts..
Smart pointers were made for allocating memory on the heap. But if this happens for "character logic"/engine, if the compiler can't transpose that memory to CPU caches, then a giant performance loss will happen. What I use to do is to use a std::vector, which has a mostly identical syntax to an std::array or a "static_vector" (a kind of std::vector for caches). Then, if performance goes wrong, I simply change the container type - this is just 1 line of code, if aliases were created.
odin has comp time too btw, you just prefix a parameter/struct field with a $ and there you go. You can constrain them with where clauses too, its really nice Edit: this makes it also really nice to create an ecs in odin
"Building an ECS" from Sander Mertens is a nice series that explains some concepts and implementation details. I'm currently building my own variant with Zig Comptime that automatically creates all the necessary data structures as SOA just from the struct types. Systems are simple functions that are automatically called for each entity that contains the requested components. At runtime it is just a bunch of loops over SOAs and function calls, no runtime reflection. It's not fully featured but a very good exercise.
I'll probably open source it when I have the basic features completed. No blog post yet, but take a look at the series that I mentioned above. Main difference is that I want a more static approach, instead of archetypes that are defined at runtime they are fixed. This prevents implicit copying things around if components are added/removed (you would have to do that manually by defining all variants or choosing another representation), which I think fits better with Zigs explicit nature. Especially many short-lived components for e.g. for debuffs can otherwise quickly lead to performance problems. Other than that you could take a look at Flecs (C, there is a binding for Zig called zflecs) or the ECS from Bevy (Rust).
This actually connects back to the thing about whether or not you should comment your code; very few comments should survive the exploratory phase of programming, but comments are pretty important FOR the exploratory phase. Its something you fortify shakey code with, and either the code changes, or the comments go away. Sometimes a little of a and b both. The ultimate end-all-be all impossible to prototype in language would be one of those 'true by construction' type languages that even have a special structured editor (as opposed to text editors) where theres no such things as compile errors because you cannot even express an invalid program. Sounds great because a large class of bugs are no longer even representable in the language; its inherently more safe. But you basically already have to have written the program in another language and debugged it there before you can bring it into this stricter system. Debugging isn't just a processes for fixing your programs. I would say it isn't even PRIMARILY a process for fixing your programs. Its primarily a process for fixing your conception/understand of them. You need an environment to explore bad programs before you can get to the good ones. Its not enough for some tool to tell you one time 'yeah this is good'. You need to build up confidence iteratively.
6:00 in C# you can use Roslyn Analyzers to do this kind of stuff (custom static code analysis). Of course it won't fulfill the exactly same role as in Jai because you're still dealing with all other stuff of C#.
While Rust provides correctness checking by building it into the compiler (as part of the language itself), Jai let's programmer's extend the compiler through it's metaprogramming capabilities and achieve, for example, similar correctness checking to what Rust offers, if one happens to want this. I wonder if I am understanding this correctly. And if so, isn't this kind of genius?
Jon has never programmed in Rust. So it is hard for take his opinion seriously. From listening to the "prime", it seems he doesn't know Rust very well either. His example with the "strings" is weird. If you want to experiment with strings, use String, clone and then when you know exactly what you want change it to &str, Cow or whatever.
@@rytif 1. I don't care how long it takes to compile. I've shipped projects with a thousand dependencies and it compiled in about 40-50 seconds.The compile times are slow but not so slow that you can't work. I care about the runtime. 2. Iteration is terrible, if you don't know the language well or you are a bad programmer.
Having the borrow checker as a compiler flag would be pretty cool. All the exploratory programming and then turn it on when you locked things down/keep it was warnings. Similar to how lisp does it with type-checking when you turn on optimizations (for SBCL at least).
Bad idea. You'll likely end up with a pile of garbage rather than a beautifully refactored chunk of code. It's definitely not fun to suddenly get a hundred errors from a compiler and spend an hour or two fixing those. Even if you end up with something decent, the tedious and non-creative process of this kind of refactoring would eventually wear you out, you'll ditch the borrow checker and just stop caring about correctness that much. Not to mention the disappointment when your beautiful experimental code simply cannot be refactored to satisfy the borrow checker, because of some subtle but fundamental flaw in the algorithm. Optional correctness never works. Just look at C and C++. You can absolutely write perfectly safe code in these, just gotta follow all the million rules in the standards. They've got valgrind, various sanitizers, static analyzers and whatnot... it all doesn't matter much, 95% of that code is still crap, because all these tools are optional.
I remember one cool thing about jai metaprogramming which was a metaprogramming system for integrating functions into the game console. I don't recall the details exactly but from memory it inspected the functions that were passed to it and automatically generated help text and input value correctness checking for the associated console command based on the function arguments and generated code to be run to register the console command later during that same compilation. So the method for adding a console command is just to declare that the function should work in the console with a description of what it does and the metaprogram does the rest of the work. Of course I'm so hazy on this maybe it was just a dream.
@@notuxnobux "It's done at compile time instead of runtime" Which makes it quite a bit less interesting. I mean you could do the same thing even with C macros
I think that is kinda what Zig is doing when you switch between release, release fast, debug and debug fast. A lot of the magic in Zig is this type of comptime loops, at least that is my understanding.
I'd like to see Jon on the show. I've been watching his videos from over the past 9 years. He is good at thinking about ideas down to the CPU/RAM/cache and deciding why it would or wouldn't work.
with ts i love the comination of super easy concurrency and type safety so much...that plus being able to roll it out to the web and have access to all those tools, for frontend, my goto
I think this only sometimes true. I made a multithreaded game engine as my first Rust project, as an experiment. Making anything multithreaded in Rust is way way way way easier and faster because you aren't spending a shitton of time solving race conditions. For projects that don't really have "bug filled traps" like multithreading and other stuff then ya better to use C# or something. The other thing is if your experiment is gated by performance. If you're trying to build a crazy simulation, you might not get to experiment with the stuff you want to because its too slow. Same with games. "Ah I can't experiment with a like 10000 unit RTS game unless performance is awesome out of the box".
Writing compiler time rules after the fact sounds like what we're all using unit tests (in untyped languages) for now. That sounds neat, especially if it can be integrated with an LSP for realtime notification that I'm breaking rules.
@ThePrimeTime Now that JAI is a little more open, you can probably ask for access. Randy (yes that Randy) is using it currently for his game so maybe he could take the time to show you some stuff.
13:50 "Limited amount of memory" I don't know when we got to the point where to open up just a single browser window requires 400-500mb of RAM. I tried with several different browsers. It feels like **something has gone terribly wrong**. We just build 💩 on top of a foundation of more 💩.
I just had a discussion at work, that we need to increase request size limit, we were like why? 60MB was not enough for one of our POST endpoints. Turns out we had a product, that had a company, and the company had products.... And nobody noticed we post this shit back and forth for the las couple years, but lately we handle enough products for this to become a problem :D
7:00 comptime generics are great for Zig, but not for most languages as discussed on the blog post "Zig-style generics are not well-suited for most languages" Whether or not you like a more limited use case version of generics fit for your program and how much you love other kinds of generics is gonna determine whether you love zig generics.
@@rytif Or... maybe let people choose if they need it or not. What's the issue with people wanting to use ECS getting suggestions that bothers you so much?
Yes! Cost of experimentation is my worry with Rust, especially coming from TS. You can make up for it with better pen and paper structured problem analysis but it has its costs.
Almost all of Nim can run at compile time. Arbitrary (and incredibly clear) compile logic is easy. And const compile-time definitions make some really wild stuff possible (i.e. reading a file into a string and including it as a const in your compiled binary). I definitely avoid macros, though.
19:15 ish noob q here: Aren't Rust lifetimes a solution to that problem? by using matching lifetimes on matching entities that are supposed to live and die at the same time?
Precisely. In fact, you can do exactly what John Blow describes with an arena allocator crate (and I have seen it down in compilers for pass-local structures). All data allocated into the arena would get references with the lifetime bound to the arena, and if "the noob intern" ended up shoving one of them into a long-lived data structure, you'd get an instant type error. There are, however, a few more complexities that make this a bit tricky in practice (and why entity indices may be the superior approach). One is, for example, it may require some tricky management of the mutability access on these pointers: say allocating in the Arena gives you back a &mut T, then there is no easy way to go down into a &T and then back up to a &mut T safely. With an index, the problem is "punted" down to the access on the container, not on the pointer itself (as pointer = container[index]) Finally, the obvious way for doing this requires the "game" to be written in a way that controls the main loop. If you are making your game in something like "standard bevy", you don't have control over that loop, hence no control over the per-frame scoping. So, I think, ultimately, entity indices are the way to go. Insisting on entity pointers makes sense once you are in something like C++ (though I disagree on this as well...). This is fine, but it leads to the non-surprising conclusion that "I wouldn't use Rust for this C++-style solution"
The compilation logic he is talking about is probably for porting to different platforms, GPUs and different versions OpenGL/DX3D/Vulkan have a lot of small differences between each that are a pain to work with. For example, big commercial game engines each have their own different set of conventions they adhere to and they each have to translate these to different versions of OpenGL, it's a mess.
@@freesoftwareextremist8119 Yes. Great Language, but JVM has a great ecosystem for modern applications, so the ability to leverage that while maintaining some measure of lispiness is quite nice.
I recommend watching the video where john blow made a sad edit of himself saying how noone understands the meaning of braid while playing clips of soulja boy playing braid saying the game is about jumping
"This guy made one successful video-game and now acts like he's the next Linus Torvalds." Best most accurate comment about JB I've ever read on youtube.
It’s not accurate at all.. he made 2 successful games. And tbf, his first game Braid was really ground breaking and helped kicked off indie game development
With Python I always lose so much time on stupid runtime errors caused by some small nonsense that I cannot find the cause for hours. So I stopped prototyping in it. I never have this problems with statically typed languages.
Blow actively advocates *against* ECS. The reality is that ECS is just the new OOP: Overcomplicated premature over-generalization that usually doesn't solve your actual problems, generates new ones and makes everything 10x more complicated than it needs to be. And I say this after having used an ECS on a 3-year commercial game project. I do not regret using ECS, it's fine. But it's way more complicated than it needs to be. Keep stuff simple.
I work on memory constrained machines. The one I use a lot has 4K of flash, and 256 bytes of RAM 🙂But I work in embedded stuff. I'm just here for the lolz.
I get your excitement for comptime, but I don't think it's a good replacement for rusts generics. The amazing thing about rusts generics is that they are type-checked using traits. I can look at a generic function and know exactly what I am able to do with the generic argument in its body without introducing a breaking change.
I’ve been thinking it’d be nice to have a easy rust. You could write any module in easy rust, which is just rust, but everything that would be on the heap is simply Rc so you never think about that or boxing etc. All strings are String. You could write your module in this, and then when it needs to be fast, change the file from ers to rs and add in all the big boy code. It would interact with rust no problem you just have a non optimal but python like experience but it’s still rust just without a bunch of choices Someone please steam my idea
Kinda. Comptime allows for manipulating types as values (you can pass a type as a function parameter, you can return it from a function, you can create new types during compile time) and executing code during compile time. It is basically a replacement for templates, constexpr and (some) macros, all in one and more "elegant".
What would happen if we'd wrote programs by just defining tests. And let ChatGPT iterate till the tests are passed. What would the world look like in such scenario?
I actually asked ChatGPT about this idea awhile back. It suggested that it'd require very thorough and precise unit tests and would be very resource intensive. But I think someday we'll get there, with humans doing higher-level architect work and AI doing the construction work.
the general concept is akin to 'theorems for free' style type-derived programs, where you specify the type so narrowly that there is exactly one program that the type could satisfy. The world wouldn't look any different really. At least in this version of reality where the type entirely describes the runtime behavior of the program. You just have big disgusting types/tests that obscure the meaning of the program
ECS is literary destroyed during the optimization step of the video game development cycle. Moreover, it's quite common to use fix allocation, even for objects with different sizes, which allow to achieve better performance. In rust this is hard or even impossible (just in unsafe mode?). In this context, Zig could be a good alternative, but it leaks of operator overloading, and for linear math is very bad.
"ECS is literary destroyed during the optimization step of the video game development cycle. " why would you say that? Have any concrete examples, sources?
@@empireempire3545 In short: to avoid memory fragmentation. Indeed, game optimization often involves "component fusion," merging components with entities to enhance memory and cache performance. An example is the Unity "transformation-component", it is a fake component (indeed it is mandatory), or an other example is the Actor class in UE (which has a lot components/behaviors build in).
You mentioned utilizing smart pointers in Rust for app development. Could you expand a little on the use case there? I feel like I don't take enough advantage of smart pointers. I'm primarily an app/web developer
Smartpointers are for languages without a garbage collector. If you're a web dev, you most likely always use GC. The most used ones allow the code to hold multiple references to the same piece of data while not leaking memory (or doing the memory management manually). Others make sure that there's only a single reference. Some might do other stuff like closing a network connection when a piece of data isn't used any more.
Jonathan Blow does a lot of good interviews, I'm sure he'd love to be on here actually. He did some great interviews with No Boilerplate, and No Frauds Club.
Where can I watch/listen to these? :o This sounds really interesting!
@@gameofpj3286 here on UA-cam, I can't link directly
The invisible reply is probably someone linking them
Get Kasey and Jonathan on here at the same time!
I can't find the interview with No Boilerplate. Can you point me in the right direction?
In that talk I get the impression that he actually respects rust but that it is not the right tool for for him or game design in general. I also get the impression that while he is an opinionated man willing to express those opinions that he is not mean and he is happy to hear out/encourage other opinions without belittling them.
Your impression resonates with me
I think anyone serious about making a language has to understand and respect what other language designers did. But on the other hand being opinionated helps since basically everything you decide for a language is an opinion you force onto other. So forming opinions and being informed of opinions are required skills for the job.
I'm a regular on his streams for years now and somewhat disagree. If he wants to, he can be nuanced and say his opinion in a way that is very hard to fault, but most of the time, he doesn't want to. And the outcomes of that make me sad sometimes. If someone comes to the stream, asks a random question, and gets absolutely trashed for it, that's not good. He can be mean.
@@simivb Certainly, he's no Carl Sagan. But in the Witness, he featured James Burke and other decent science communicators (like astronauts) and I think it illustrates that he may want to be approachable, but just have limited capacity/patience to practice it. I'm giving him the benefit of doubt, but it's certainly a shame, given Jai has a lot a potential - at this point I expect it to be in closed beta until some entity creates a superior publicly available one.
I've listened to a lot of JBlow's stuff, and yes, he does respect Rust. In some of his earliest lectures on what he wants from a programming language (now Jai) he mentions it several times. He likes that it is trying to have a strong and unique opinion about how programming could, or should, work. A lot of modern languages are just syntactical sugar or supersets of pre-existing languages, with very few, if any, truly innovative ideas. He respects that Rust is doing something different.
As for opinions, well he can definitely be mean/abrasive when he's in a bad mood lol. He often parrots a gate-keeping mentality and in my opinion, it doesn't do him any favors. I think its a bit of a shame because he is an absolutely brilliant mind, and I can highly recommend watching any of his talks if you're into game design/development.
For clarity, when Jon Blow talks about "entities", he's not referencing to any sort of ECS mechanism like 98% of the time. The term "entity" is as generic of a term as the "variable" in his working style. They are just a bunch of collection of data that represent things that can exist in a game world. That's all. Don't think too hard.
Another way of saying the same thing: when a Java developer talks about an object, they're not talking about a thing whose (narrowest) type is `java.lang.Object`, but rather... any instance of any class. Entity is similarly general, although along a slightly different axis: it's any obj... uh... _thing_ which exists in the game world. A player, a monster, a tree, a wall, a vehicle, whatever.
Entities is another word for an object, but like an object in the real world like a cup or something. Its a thing that can Normally be interacted with someway in the game world.
Its really really annoying that programmers steal random words for random shit. Wish we could just use the word object, but that means a bunch of very different things now.
the scope of the generality is misunderstood by people - the monster is an entity, the tile the monster is in is also an entity, one is in a queue, the other in an array .. an entity is much more general that "object"@@jonaskoelker
I think it's the difference between
```
-- init Object with data
object = {position=V3(1,2,3), health=42, maxHealth=50, active=true}
allObjects[#allObjects+1] = object
-- later
for _, o in allObjects do
if o.active then
o.Health = o.maxHealth
end
end
```
and
```
-- init entity data
n = entityCount
positions[n] = V3(1,2,3)
healths[n] = 42
maxHealths[n] = 50
activeEntities[#activeEntities+1] = n
entityCount += 1
-- later
for _, eid in activeEntities do
healths[eid] = maxHealths[eid]
end
```
this allows a direct index to get or set specific-property data by id (number) rather than (object.property)
consider the difference:
given N objects with M properties each, there will be N * M + N different data-containing-structures.
whereas
given N entities with M properties, there will be M different data-containing structures.
In my experience (significant amount) this is meaningful to bottom-line (what can happen in a frame) when doing simple operations on ~million things 60 times a second. It becomes more important when there are interactions between entities that can be compressed down to operations on data.
tldr;
object and entity are different concepts:
objects have more wrapping paper,
entities have large wrapping papers that each cover a lot at once
Prime's love for JBlow runs deep. He not drinking the koolaid, he snortin the blowcaine
I guess you didn't watch the full video
I would blow Blow
@@shinjite06🫡
7:45 This is what Common Lisp macros are, just Lisp code running at compile time that returns Lisp code. The next benefit they have over other comp time and macro systems is that all Lisp code is already written as Lisp objects, so there's no special operations or types needed to represent code.
This argument for ease of experimentation is why I love to do first drafts in Python, if it turns out that you need more performance, rewrite it in cpp, rust, or whatever systems language. Reciently I had to build an SPI to webrtc bridge to display stats from a DSP in real time, guess what python is slow and there were some bugs I had to solve in aiortc, but in the end it helped me to understand how the whole webrtc signaling process works. Now I'll be rewriting the bridge in rust with a more solid understanding of the process.
Most of the time the python implementation is more than sufficient through, which saves me a lot of dev time.
that's the problem though - most of the time you won't rewrite it, so now you have a program in python, with all the problems that go along with that, when you originally wanted a program in cpp, rust, or whatever systems language, and planned to just write a prototype in python. I can virtually guarantee that, while the prototype might be slower to get out if you write it in cpp or whatever, it will take less time overall to write a prototype in cpp and then convert that to a final version in cpp, compared to writing a prototype in python and a final version in cpp.
Regarding the "difficulty of trying stuff out", I remember watching a video where a C++ developer said he used Python for prototyping because it's easier that way. Once he found an solution he liked, he then implemented it in C++.
the blender is made that way nowadays. ig even freecad too.
Can confirm. There was a thing I wanted to write in C, let's pretend it's a Sudoku generator (it's not too far from being concretely true). I had various strategies that partially worked, some of the time, but frequently enough I would generate a puzzle my solver couldn't solve.
I used python to prototype various ideas. Failure-and-retry probabilities served as a good enough predictor of performance. Once I got an algorithm I was okay with, I implemented it in C and it worked as expected.
Note that the output as a function of input is probably the same in C vs. python, but the performance is not. So be careful about extrapolating; Think about when the prototype is and isn't a good predictor of the characteristics of the production implementation.
When is a python prototype the right approach? Intuition says: the longer the exploratory process is, and the more faster you can try ideas out in the prototype language compared to production, and the less time it takes to rewrite.
Let's math it out. Say the time cost of implementing x features in language 1 is a_1*x + b_1 and in language 2 it's a_2*x + b_2, the cost of rewriting the final version is r and you need to implement at least t features before your prototype is done and you can begin rewriting.
Then the cost of writing in language 1 is a_1*t + b_1; the cost of prototyping first is a_2*t + (b_2 + r). For prototyping to win, you want a_2*t + b_2 + r < a_1*t + b_1, which is equivalent to b_2 + r - b_1 < (a_1 - a_2)*t.
This agrees with intuition: the numbers which favor prototyping are greater t (more exploratory process), greater a_1 - a_2 (development time: python beats Scala beats C++), lower rewriting and differential fixed costs.
Of course, you can never observe the numbers and so you never know. You can do both, interleaved, and "only" spend twice as much time as the fastest approach. Or you can make a judgement call based on experience.
If you're doing something small or just starting out a project, sure, but if you want to prototype/explore something inside a 1 million line C++ codebase you can't really whip out the python.
@@alexvitkov I generally agree-although one should consider defining some interface around the place where you want to introduce new behavior, which only takes plain data in and gives plain data out.
Then you can prototype a python thing which does the right input/output mapping of the data, then rewrite it in your production language once you have the right implementation strategy.
This becomes harder if you want to prototype something which reads and/or updates the system state. It's doable: send over all the data you could ever want to read; return a list of descriptions of things to update. Probably requires a lot of elbow grease and the amount of transferred data may be too large to be practical. But sometimes this can work, and hey this technique might be one that works.
Python allowed me to understand and learn more complex concepts a lot faster and easier. I then find out what the equivalent of those are in c++ and understand them a lot better.
I love Jon and I always appreciate his opinions. He's not always right and he's not always making the best case. But he's always concise about how he feels and always brings great ideas into the conversation.
I'm a simple man, I see a Joblow vid, i watch.
Ama simpler man, I see BlowJo - I watch.
gaaaaaeeeee
Joe Blow blowing Joe's bros
simplest man; i just watch.
I miss the On The Metal podcast. Every guest had some amazing anedoctes and insights. Even though JBlow's interview was 3 hours long, I didn't want it to end. Every single episode was like that.
Rewrite it in Rust unironically the best approach to writing Rust ?
unfortunately...
Oh god, thats the second reaction to jon blows stuff that contains basically the entire buildup to a point he is making, and then just stops before he makes the point. Im dying out here, what is this
Right???
I think a cool case for zig comptime would be parsers. You can get a type safe interface for some file format (think schemas) at compile time without codegen or code first type inference (like zod in typescript)
7:10 I was just actually watching a video on comptime, etc, in Zig, and, coming from C++, Zig has started to feel kind of like that smaller, beautiful language that is supposed to be struggling to get out of C++. I know it's supposed to be like a modern C, but it feels more like what C++ was supposed to be. Like for example, constexpr & consteval gets us in the realm of comptime-- that idea of a first class function -- *not a macro* -- that does compile time calculations to generate some code or some compile time variable. And then we also have the ultra easy interop with C, literally no effort apparently. And defer! Oh gosh, defer looks so nice. It just feels like the same sort of area of why Bjarne made "c with classes". The niceties just shifted from being actual literal builtin classes and polymorphism to all the zig niceties. Ya know? idk. just some thoughts..
Smart pointers were made for allocating memory on the heap. But if this happens for "character logic"/engine, if the compiler can't transpose that memory to CPU caches, then a giant performance loss will happen.
What I use to do is to use a std::vector, which has a mostly identical syntax to an std::array or a "static_vector" (a kind of std::vector for caches). Then, if performance goes wrong, I simply change the container type - this is just 1 line of code, if aliases were created.
I was *just* on a primeagen JBlow binge! Yes! More! Love Jai
Oh, it's that 2020 interview. Meh :/
odin has comp time too btw, you just prefix a parameter/struct field with a $ and there you go. You can constrain them with where clauses too, its really nice
Edit: this makes it also really nice to create an ecs in odin
Odin rocks
Can't wait for John to be on the stream!
"Building an ECS" from Sander Mertens is a nice series that explains some concepts and implementation details. I'm currently building my own variant with Zig Comptime that automatically creates all the necessary data structures as SOA just from the struct types. Systems are simple functions that are automatically called for each entity that contains the requested components. At runtime it is just a bunch of loops over SOAs and function calls, no runtime reflection. It's not fully featured but a very good exercise.
oh nice, do you have it open sourced? also maybe some blog post?
I'll probably open source it when I have the basic features completed. No blog post yet, but take a look at the series that I mentioned above. Main difference is that I want a more static approach, instead of archetypes that are defined at runtime they are fixed. This prevents implicit copying things around if components are added/removed (you would have to do that manually by defining all variants or choosing another representation), which I think fits better with Zigs explicit nature. Especially many short-lived components for e.g. for debuffs can otherwise quickly lead to performance problems. Other than that you could take a look at Flecs (C, there is a binding for Zig called zflecs) or the ECS from Bevy (Rust).
I listen to that interview just for fun sometimes, it's so good. Recommended in its entirety!
JBlow doesn't use ECS. He talked many times about how your game doesn't need ECS. Just write the code for entities you have. That's what he does.
This actually connects back to the thing about whether or not you should comment your code; very few comments should survive the exploratory phase of programming, but comments are pretty important FOR the exploratory phase. Its something you fortify shakey code with, and either the code changes, or the comments go away. Sometimes a little of a and b both.
The ultimate end-all-be all impossible to prototype in language would be one of those 'true by construction' type languages that even have a special structured editor (as opposed to text editors) where theres no such things as compile errors because you cannot even express an invalid program. Sounds great because a large class of bugs are no longer even representable in the language; its inherently more safe. But you basically already have to have written the program in another language and debugged it there before you can bring it into this stricter system.
Debugging isn't just a processes for fixing your programs. I would say it isn't even PRIMARILY a process for fixing your programs. Its primarily a process for fixing your conception/understand of them. You need an environment to explore bad programs before you can get to the good ones. Its not enough for some tool to tell you one time 'yeah this is good'. You need to build up confidence iteratively.
I like the idea of using Ada and Spark. Where you can enforce many checks statically and its is a procedural language so it is also easy to use.
6:00 in C# you can use Roslyn Analyzers to do this kind of stuff (custom static code analysis). Of course it won't fulfill the exactly same role as in Jai because you're still dealing with all other stuff of C#.
Paused it right before he explained how Jai's meta programming helps. It's literally written in the transcript you didn't read.
"now that Jai is out there" what the hell did I miss??
While Rust provides correctness checking by building it into the compiler (as part of the language itself), Jai let's programmer's extend the compiler through it's metaprogramming capabilities and achieve, for example, similar correctness checking to what Rust offers, if one happens to want this. I wonder if I am understanding this correctly. And if so, isn't this kind of genius?
Jon has never programmed in Rust. So it is hard for take his opinion seriously.
From listening to the "prime", it seems he doesn't know Rust very well either.
His example with the "strings" is weird. If you want to experiment with strings, use String, clone and
then when you know exactly what you want change it to &str, Cow or whatever.
@@rytif 1. I don't care how long it takes to compile. I've shipped projects with a thousand dependencies and it compiled in about 40-50 seconds.The compile times are slow but not so slow that you can't work. I care about the runtime. 2. Iteration is terrible, if you don't know the language well or you are a bad programmer.
Having the borrow checker as a compiler flag would be pretty cool. All the exploratory programming and then turn it on when you locked things down/keep it was warnings. Similar to how lisp does it with type-checking when you turn on optimizations (for SBCL at least).
Technically I belive it is. I may be wrong but you can compile without doing borrow checking.
Bad idea. You'll likely end up with a pile of garbage rather than a beautifully refactored chunk of code. It's definitely not fun to suddenly get a hundred errors from a compiler and spend an hour or two fixing those. Even if you end up with something decent, the tedious and non-creative process of this kind of refactoring would eventually wear you out, you'll ditch the borrow checker and just stop caring about correctness that much. Not to mention the disappointment when your beautiful experimental code simply cannot be refactored to satisfy the borrow checker, because of some subtle but fundamental flaw in the algorithm.
Optional correctness never works. Just look at C and C++. You can absolutely write perfectly safe code in these, just gotta follow all the million rules in the standards. They've got valgrind, various sanitizers, static analyzers and whatnot... it all doesn't matter much, 95% of that code is still crap, because all these tools are optional.
Memory is incredibly limiting when making an intermediate-to-large game (and some niche kinds of small games).
We need more J Blow's in tech.
We need more BlowJobs indeed.
You know what I need more?
Blow J
I remember one cool thing about jai metaprogramming which was a metaprogramming system for integrating functions into the game console. I don't recall the details exactly but from memory it inspected the functions that were passed to it and automatically generated help text and input value correctness checking for the associated console command based on the function arguments and generated code to be run to register the console command later during that same compilation.
So the method for adding a console command is just to declare that the function should work in the console with a description of what it does and the metaprogram does the rest of the work.
Of course I'm so hazy on this maybe it was just a dream.
That part is actually similar to Java properties, except in jai it's done at compile time instead of runtime
@@notuxnobux "It's done at compile time instead of runtime" Which makes it quite a bit less interesting. I mean you could do the same thing even with C macros
I think that is kinda what Zig is doing when you switch between release, release fast, debug and debug fast. A lot of the magic in Zig is this type of comptime loops, at least that is my understanding.
I'd like to see Jon on the show. I've been watching his videos from over the past 9 years. He is good at thinking about ideas down to the CPU/RAM/cache and deciding why it would or wouldn't work.
Jai's megaprogramming is the Lego Technic to Zig comptime's Duplo. Both are great, but the maturity delta is significant.
I will believe it when i can actually use it
The rest of the episodes on this podcast are fantastic and would love to see prime react to more of them
with ts i love the comination of super easy concurrency and type safety so much...that plus being able to roll it out to the web and have access to all those tools, for frontend, my goto
The battle of C family C/C++ vs Zig vs Jai vs Odin vs Carbon vs V vs Beef
CL-style defmacro statements are still probably the most powerful way to metaprogram
JB is the real OG. He knows his stuff.
Whats the point of seeing Prime's reaction to JBlow clips since they practically agree on most of the covered topics?
I think this only sometimes true.
I made a multithreaded game engine as my first Rust project, as an experiment.
Making anything multithreaded in Rust is way way way way easier and faster because you aren't spending a shitton of time solving race conditions.
For projects that don't really have "bug filled traps" like multithreading and other stuff then ya better to use C# or something.
The other thing is if your experiment is gated by performance. If you're trying to build a crazy simulation, you might not get to experiment with the stuff you want to because its too slow. Same with games. "Ah I can't experiment with a like 10000 unit RTS game unless performance is awesome out of the box".
Cover his takes on women in STEM and Covid next
Writing compiler time rules after the fact sounds like what we're all using unit tests (in untyped languages) for now. That sounds neat, especially if it can be integrated with an LSP for realtime notification that I'm breaking rules.
@ThePrimeTime Now that JAI is a little more open, you can probably ask for access.
Randy (yes that Randy) is using it currently for his game so maybe he could take the time to show you some stuff.
If you like zig comptime, you will love OCaml functors.
13:50 "Limited amount of memory" I don't know when we got to the point where to open up just a single browser window requires 400-500mb of RAM. I tried with several different browsers. It feels like **something has gone terribly wrong**. We just build 💩 on top of a foundation of more 💩.
I just had a discussion at work, that we need to increase request size limit, we were like why? 60MB was not enough for one of our POST endpoints. Turns out we had a product, that had a company, and the company had products.... And nobody noticed we post this shit back and forth for the las couple years, but lately we handle enough products for this to become a problem :D
6:19 what he is referring to is static introspection. You can do that in Nim today
7:00 comptime generics are great for Zig, but not for most languages as discussed on the blog post "Zig-style generics are not well-suited for most languages"
Whether or not you like a more limited use case version of generics fit for your program and how much you love other kinds of generics is gonna determine whether you love zig generics.
Thanks for the reading suggestion
If you want to get a proper grasp of ECS, try the Bevy game engine (in Rust). It's entities and components all the way down.
@@rytif Or... maybe let people choose if they need it or not. What's the issue with people wanting to use ECS getting suggestions that bothers you so much?
Yes! Cost of experimentation is my worry with Rust, especially coming from TS. You can make up for it with better pen and paper structured problem analysis but it has its costs.
this is why i am really loving ocaml
Prime: "I'd rather just write Rust code executed at compile time that generates code"
Yea. That's what procedural macros are.
@9:00 So... like C++ templates, constexpr, etc.
11:25 11:30 11:50 ECS entity-component-system
Almost all of Nim can run at compile time. Arbitrary (and incredibly clear) compile logic is easy. And const compile-time definitions make some really wild stuff possible (i.e. reading a file into a string and including it as a const in your compiled binary). I definitely avoid macros, though.
JBlow makes all the right people salty asf, plus he made Braid.
He makes enough people mad that he's gotta be right.
If anyone agrees to you, why even speak, right?
if i punch everyone i see... i too must be right.
he's certainly right about some stuff. but i doubt thats why anyone has issue with the things he says.
19:15 ish noob q here: Aren't Rust lifetimes a solution to that problem? by using matching lifetimes on matching entities that are supposed to live and die at the same time?
Precisely. In fact, you can do exactly what John Blow describes with an arena allocator crate (and I have seen it down in compilers for pass-local structures). All data allocated into the arena would get references with the lifetime bound to the arena, and if "the noob intern" ended up shoving one of them into a long-lived data structure, you'd get an instant type error.
There are, however, a few more complexities that make this a bit tricky in practice (and why entity indices may be the superior approach). One is, for example, it may require some tricky management of the mutability access on these pointers: say allocating in the Arena gives you back a &mut T, then there is no easy way to go down into a &T and then back up to a &mut T safely. With an index, the problem is "punted" down to the access on the container, not on the pointer itself (as pointer = container[index])
Finally, the obvious way for doing this requires the "game" to be written in a way that controls the main loop. If you are making your game in something like "standard bevy", you don't have control over that loop, hence no control over the per-frame scoping.
So, I think, ultimately, entity indices are the way to go. Insisting on entity pointers makes sense once you are in something like C++ (though I disagree on this as well...). This is fine, but it leads to the non-surprising conclusion that "I wouldn't use Rust for this C++-style solution"
The compilation logic he is talking about is probably for porting to different platforms, GPUs and different versions OpenGL/DX3D/Vulkan have a lot of small differences between each that are a pain to work with. For example, big commercial game engines each have their own different set of conventions they adhere to and they each have to translate these to different versions of OpenGL, it's a mess.
Can't wait to watch the stream where you build an ECS. :)
ECS is the best architecture we have atm imo. Not just for games, for many, many things.
JBlow slags off Lisp, but clojure has generative testing and spec which kind of do what he wants to do
OK man hear me out, Clojure is fine... But have you tried CL?
@@freesoftwareextremist8119 Yes. Great Language, but JVM has a great ecosystem for modern applications, so the ability to leverage that while maintaining some measure of lispiness is quite nice.
Pretty funny that all the comments in his stream are "What about Lisp?", Prime proceeds to ignore Lisp's existence 🙂
I recommend watching the video where john blow made a sad edit of himself saying how noone understands the meaning of braid while playing clips of soulja boy playing braid saying the game is about jumping
He didn’t make that clip. It was clear mockery.
Was the part where he's against a window in a dark room like a CIA informant saying how they misunderstood his art filmed without his knowledge lol?
That wasn't made by him. It was an indie games documentary
Ha! Say "entity" instead of "object"?! Because OOP is so evil, right? Let's just call it something else. Ha!
3:03 "And boom! You are good to Go!"
comptime is a replacement for generics, and also macros.
My programming pipeline is mock up in javascript then straight to assembly for implementation 😂
The 10th time I've heard prime say how great it would be to write a CSV parser in rust...
XSV?
6:39 how times change wrt zig & comptime & rust & macros in prime's opinion :D
I'm so scrolling what the host says just to hear snippets of what Jon says before listening to the whole podcast.
"This guy made one successful video-game and now acts like he's the next Linus Torvalds."
Best most accurate comment about JB I've ever read on youtube.
It’s not accurate at all.. he made 2 successful games. And tbf, his first game Braid was really ground breaking and helped kicked off indie game development
@@ZombieLincoln666and has no documented control scheme on Netflix, but "Hey!" it's a puzzle game, right?
With Python I always lose so much time on stupid runtime errors caused by some small nonsense that I cannot find the cause for hours. So I stopped prototyping in it. I never have this problems with statically typed languages.
5:41 like noImplicitAny in TS, but better. Or like having a GC mode and a borrow checker mode on a module level.
Blow actively advocates *against* ECS. The reality is that ECS is just the new OOP: Overcomplicated premature over-generalization that usually doesn't solve your actual problems, generates new ones and makes everything 10x more complicated than it needs to be. And I say this after having used an ECS on a 3-year commercial game project. I do not regret using ECS, it's fine. But it's way more complicated than it needs to be. Keep stuff simple.
You can make ECS as simple as you want: a World struct with a bunch of Vecs for each entity type and you're *nearly* there.
He literally paused and then stopped right before Jonathan arrived at the main point he was building to.
CSV parser is an S-program: solving a well specified problem
I work on memory constrained machines. The one I use a lot has 4K of flash, and 256 bytes of RAM 🙂But I work in embedded stuff. I'm just here for the lolz.
What chip has those specs? The weakest mcu I've used is the cortex m0
fyi, though its rarely mentioned, all these zig/odin/vlang/etc. langs are just clones of jai --- it's a shame jblow let that get away from him.
If something is a clone, it does not mean that it is worse than its original though.
JB always says "right?" in such peculiar spots
I get your excitement for comptime, but I don't think it's a good replacement for rusts generics. The amazing thing about rusts generics is that they are type-checked using traits. I can look at a generic function and know exactly what I am able to do with the generic argument in its body without introducing a breaking change.
Would be super cool if you were able to get him for an interview.
People are still making games for the Amiga and AtariST. Memory matters.
Limited amount of memory @14:00 is about VRAM / GPUs
I’ve been thinking it’d be nice to have a easy rust. You could write any module in easy rust, which is just rust, but everything that would be on the heap is simply Rc so you never think about that or boxing etc. All strings are String.
You could write your module in this, and then when it needs to be fast, change the file from ers to rs and add in all the big boy code.
It would interact with rust no problem you just have a non optimal but python like experience but it’s still rust just without a bunch of choices
Someone please steam my idea
ThePrimeTime doesn't even know how good it is.
So, basically zig's comptime is like templates and constexpr from c++?
Kinda. Comptime allows for manipulating types as values (you can pass a type as a function parameter, you can return it from a function, you can create new types during compile time) and executing code during compile time. It is basically a replacement for templates, constexpr and (some) macros, all in one and more "elegant".
What would happen if we'd wrote programs by just defining tests. And let ChatGPT iterate till the tests are passed. What would the world look like in such scenario?
I actually asked ChatGPT about this idea awhile back. It suggested that it'd require very thorough and precise unit tests and would be very resource intensive. But I think someday we'll get there, with humans doing higher-level architect work and AI doing the construction work.
An extremely high suicide rate, or no software being made cause no one wants to be just writing tests
@@TurtleKwitty so people would kill themselves because they have to write less code and do less debugging? I doubt it.
the general concept is akin to 'theorems for free' style type-derived programs, where you specify the type so narrowly that there is exactly one program that the type could satisfy. The world wouldn't look any different really. At least in this version of reality where the type entirely describes the runtime behavior of the program. You just have big disgusting types/tests that obscure the meaning of the program
4:45 is he basically describing clojure's spec and check?
Hey, Prime. Why'd you stopped Zigging? Are you in your Ocamling stage now? Will you ever Zig again?
Prime might like vim keybindings for Firefox and Chrome
Comptime sounds like a 90s hip hop group
Mad respect to Primeagen for not being yet another "JBLOW HAS NO-NO OPINIONS THEREFORE BAD" lemmings on the internet. You're a good dude.
I didn't even know he had "no-no opinions", whatever that means. My problem with the guy is he oozes incompetence ¯\_(ツ)_/¯
Would love to get updates about Jai :)
Yes, limiting amount of memory on embedded devices. That is a real thing!
ECS is literary destroyed during the optimization step of the video game development cycle.
Moreover, it's quite common to use fix allocation, even for objects with different sizes, which allow to achieve better performance.
In rust this is hard or even impossible (just in unsafe mode?).
In this context, Zig could be a good alternative, but it leaks of operator overloading, and for linear math is very bad.
"ECS is literary destroyed during the optimization step of the video game development cycle. " why would you say that? Have any concrete examples, sources?
@@empireempire3545
In short: to avoid memory fragmentation.
Indeed, game optimization often involves "component fusion," merging components with entities to enhance memory and cache performance.
An example is the Unity "transformation-component", it is a fake component (indeed it is mandatory), or an other example is the Actor class in UE (which has a lot components/behaviors build in).
@@DBGabriele I suggest you to check it out thoroughly, most of the assumptions you have seems wrong.
Ill kindly ask this... my dear J Blow...
... WHERE IS YOUR NEXT FU..ING GAME? I NEED IT!
No you don't. You think you do, but it is a really bloated and boring block pushing game.
@@____uncompetative 😦
You mentioned utilizing smart pointers in Rust for app development. Could you expand a little on the use case there? I feel like I don't take enough advantage of smart pointers. I'm primarily an app/web developer
Smartpointers are for languages without a garbage collector. If you're a web dev, you most likely always use GC.
The most used ones allow the code to hold multiple references to the same piece of data while not leaking memory (or doing the memory management manually). Others make sure that there's only a single reference. Some might do other stuff like closing a network connection when a piece of data isn't used any more.
If you use Box or Arc, you're already using smart pointers. They're not that fancy.
I've always just thought of comptime being a pre-pass on the code. Need to calculate a value based on the target OS? comptime
I actually learn a lot just from listening to you talk.