1
00:00:00,001 --> 00:00:03,520
Just before we started recording, I sprung this on you to figure out

2
00:00:03,520 --> 00:00:08,480
or to run a test how long our unit tests take to run on your machine.

3
00:00:08,480 --> 00:00:17,200
Right. And it took me way too long because I didn't realize that. Is that a new thing with

4
00:00:17,200 --> 00:00:23,120
Swift testing that it no longer crashes when the, or not crashes, but no longer fails when the

5
00:00:24,080 --> 00:00:31,040
database is not running? No, I think that's suited the way I changed the with app thing,

6
00:00:31,040 --> 00:00:37,760
you know, the way it changes in the transition to Swift testing. We can't use the setup,

7
00:00:37,760 --> 00:00:43,280
the normal setup anymore. And I think the new setup is different in that it hangs and doesn't

8
00:00:43,280 --> 00:00:51,200
crash. I forgot to add a timeout. That's about to change anyway. So that's unfortunate side effect

9
00:00:51,200 --> 00:00:56,080
of the new setup right now. By the way, is this the show?

10
00:00:56,080 --> 00:01:02,080
This is the show. We are doing a live reveal of improvements.

11
00:01:02,080 --> 00:01:08,640
It was just the way you talked about the with app thing was with no context. So nobody will

12
00:01:08,640 --> 00:01:14,640
know what that is. Just imagine there's some huge complex thing that's happening and it's

13
00:01:14,640 --> 00:01:21,600
happening differently. To give a very, very brief bit of context, we have, as we talked about last

14
00:01:21,600 --> 00:01:27,920
episode, we've recently switched the project across to Swift testing. And it used to be the

15
00:01:27,920 --> 00:01:33,920
case that if the Docker container with the Postgres database that we used to run our database

16
00:01:33,920 --> 00:01:41,040
tests was not running, a test would fail. And you'd know whether it was, well, the first thing

17
00:01:41,040 --> 00:01:44,960
you would do is you'd spot that it was a database connection error and you'd go launch the database.

18
00:01:44,960 --> 00:01:52,240
But that is no longer the case due to, as Sven was saying, the new way that we're setting up

19
00:01:52,240 --> 00:01:58,480
the test there. So the very first time that I ran it, it took about 20 minutes because the database

20
00:01:58,480 --> 00:02:05,440
container wasn't running. - After how many minutes did you think something was wrong?

21
00:02:05,440 --> 00:02:09,840
- Well, I was doing other things at the same time. So I came back to check on it every now and again.

22
00:02:10,160 --> 00:02:18,640
So, but yes, with Xcode 16.2 and the no parallel flag passed to the tests, it was 40.6.

23
00:02:18,640 --> 00:02:25,200
- So the reason I'm asking is, as we talked about last time, one of the reasons in switching to

24
00:02:25,200 --> 00:02:31,600
Swift testing was that it will allow us to run the tests in parallel more easily, I should say. And I

25
00:02:31,600 --> 00:02:37,120
said that last time, it would always would have been possible with XCTest. It's just a lot harder

26
00:02:37,120 --> 00:02:45,360
to do with XCTest. So we use the opportunity to transition to Swift testing as a prerequisite to

27
00:02:45,360 --> 00:02:49,840
do the parallel testing. And the reason I'm asking this question, how long it takes for you,

28
00:02:49,840 --> 00:02:57,760
is to give you a really nice surprise in how fast it could run with the new branch that we

29
00:02:57,760 --> 00:03:02,720
have in the repository where parallel testing is active and working. - Fantastic.

30
00:03:02,720 --> 00:03:09,680
- And you'll be delighted to hear that on, so I think you run this on a MacBook Pro, don't you?

31
00:03:09,680 --> 00:03:19,040
- That's correct, yeah. An M1 Max. - Yeah. So on my M1 Max MacBook Pro,

32
00:03:19,040 --> 00:03:23,280
the tests now run in under eight seconds. - Wow.

33
00:03:23,280 --> 00:03:28,720
- And that's by running against eight databases in parallel. And there's no,

34
00:03:29,520 --> 00:03:35,600
there's really very, very additional performance to be gained if you increase that number. So 16

35
00:03:35,600 --> 00:03:42,400
is effectively like seven. I mean, you might get half a second faster, but it's, there's really

36
00:03:42,400 --> 00:03:49,520
nothing to be gained there. It's slightly faster still on the studio, but also more databases don't

37
00:03:49,520 --> 00:03:54,880
help a lot, but on the studio, I can get it to run in under six seconds with 16 databases under five.

38
00:03:55,600 --> 00:03:59,760
I actually, first time it ran that fast, I thought, well, is it skipping tests? But

39
00:03:59,760 --> 00:04:02,720
it's not skipping tests. - Right.

40
00:04:02,720 --> 00:04:11,520
- And I'm actually surprised that it takes 40 seconds for you running normally because it takes,

41
00:04:11,520 --> 00:04:20,960
oh yeah, I have a, actually there's a huge spread when you run it normally on a single test. I have

42
00:04:20,960 --> 00:04:27,840
35 seconds is the fastest, but I've also had it run 51 seconds. So the jitter is really quite large

43
00:04:27,840 --> 00:04:35,920
when you run the tests like normally, consecutively, serially. What I found really interesting is the

44
00:04:35,920 --> 00:04:44,720
parallel testing, they are super consistent. So they, I get 5.6, 5.7, 5.7. It's like they don't

45
00:04:44,720 --> 00:04:50,400
jitter. There's a jitter of less than half a second between iterations.

46
00:04:50,880 --> 00:04:53,840
I guess that goes down to like it being really well utilized.

47
00:04:53,840 --> 00:04:58,240
- Interesting. - And the way this is set up is that we

48
00:04:58,240 --> 00:05:03,920
spin up a fixed number of databases at the start of the test. And in this case, N is eight.

49
00:05:03,920 --> 00:05:10,400
And then the tests will, the database tests, so not all tests have to go through this, but the

50
00:05:10,400 --> 00:05:16,000
one that need a database will check against the database pool if the database is available. And

51
00:05:16,000 --> 00:05:21,040
if it is, it'll retain it, sort of grab it from the pool, run its tests. And then after it's

52
00:05:21,040 --> 00:05:25,360
finished, return it to the pool. And if there's no database available at all, a test will just

53
00:05:25,360 --> 00:05:31,040
sleep for a hundred milliseconds and then check again. So it is a sort of a polling mechanism,

54
00:05:31,040 --> 00:05:35,360
but it works well enough. And they're not, you know, they're not churning a lot.

55
00:05:35,360 --> 00:05:40,080
And that's the way that works. - And is there a lot of complexity,

56
00:05:40,080 --> 00:05:45,600
is there a lot of complexity in the code for that? - Actually there isn't, no. It's a

57
00:05:45,600 --> 00:05:55,120
single database pool actor that I added. And in the with database tests, so the thing that we had

58
00:05:55,120 --> 00:06:00,480
to introduce to make the database tests work with Swift testing, it really just asked the pool,

59
00:06:00,480 --> 00:06:08,720
give me a database. And the pool will just block and only return once the database is there.

60
00:06:08,720 --> 00:06:15,040
And that makes it really easy to swap in with the existing test runner.

61
00:06:15,680 --> 00:06:19,440
- The test itself doesn't need to be aware of that happening?

62
00:06:19,440 --> 00:06:23,920
- No, there's no change within the test at all. So the only thing that's changed is in our

63
00:06:23,920 --> 00:06:32,640
all tests suite. At the start, we set up the pool and we tear down the pool optionally afterwards.

64
00:06:32,640 --> 00:06:37,840
So that's the only change. There's really no, the tests have not changed at all. I mean,

65
00:06:37,840 --> 00:06:42,000
obviously the only thing that you do is you let them run in parallel, right? Because

66
00:06:43,440 --> 00:06:48,800
they can't run serially with that system. It could be made to work serially, but I didn't

67
00:06:48,800 --> 00:06:54,400
bother figuring out why that's not working. So the flag dash dash parallel or no parallel is

68
00:06:54,400 --> 00:07:02,640
really how you control if you run against the pool or not. So it's been quite,

69
00:07:02,640 --> 00:07:11,040
it's been fiddly, but it works really well and it's really stable. I think the biggest

70
00:07:11,040 --> 00:07:16,160
problem was that we had, as you can imagine, when you have a test suite of almost 800 tests

71
00:07:16,160 --> 00:07:25,920
that have always run sequentially, you will find stuff that is inadvertently not be made to run

72
00:07:25,920 --> 00:07:31,440
serially. So there were a couple of cases where we, for instance, had metrics and they were

73
00:07:31,440 --> 00:07:39,600
always running serially. So there was no crosstalk possible. Metrics is our system of

74
00:07:39,600 --> 00:07:44,480
having counters, you know, like request counters and stuff like that. And we have some tests that

75
00:07:44,480 --> 00:07:50,240
check that request counters are incremented properly. When tests run serially, you can just

76
00:07:50,240 --> 00:07:54,080
take the Delta and you're fine. Even if there's crosstalk. Now, suddenly that with parallel

77
00:07:54,080 --> 00:07:58,240
testing, it would happen that different tests would increment counters and the tests that

78
00:07:58,240 --> 00:08:05,280
actually look at the counters had wrong counters. So there were some parts in the tests that I had

79
00:08:05,280 --> 00:08:12,480
to make sure that we keep separate, you know, make sure that they're thread safe and adapt that.

80
00:08:12,480 --> 00:08:19,360
That was pretty much the work that had to be done to make this work reliably. And other than that,

81
00:08:19,360 --> 00:08:25,680
it's gone well. The only remaining issue is, as you can imagine, stuff like that is always harder

82
00:08:25,680 --> 00:08:32,480
in CI. I found out some weird stuff about bringing up additional databases in CI.

83
00:08:34,880 --> 00:08:39,120
They weren't discoverable as I thought they would be. The ports weren't mapped as I thought

84
00:08:39,120 --> 00:08:45,280
they would be. And there's a bit of work left to be done to make sure that the tests... Because

85
00:08:45,280 --> 00:08:52,240
when you run it locally, I use Docker commands to actually find the databases. It just runs Docker

86
00:08:52,240 --> 00:08:57,680
PS and then looks and we have a certain naming scheme for the databases. And then it can

87
00:08:57,680 --> 00:09:03,680
automatically discover what databases are there. Now our CI doesn't have Docker, so it can't use

88
00:09:03,680 --> 00:09:08,960
that mechanism. So you have to sort of prepare a set of databases where you sort of know the

89
00:09:08,960 --> 00:09:16,400
names in advance. And then it's kind of, it's more fiddly. And I completely forgot that we don't have

90
00:09:16,400 --> 00:09:22,640
Docker and we probably could make Docker available, but I don't really want to go down that rabbit

91
00:09:22,640 --> 00:09:31,600
hole. I probably just use a hard-coded names and make it work that way. But it's even debatable

92
00:09:31,600 --> 00:09:37,920
whether in CI we gain all that much because the build takes 12 minutes in CI and then the tests

93
00:09:37,920 --> 00:09:45,680
take another eight. We can probably get the eight down to maybe one or two minutes in CI, but you

94
00:09:45,680 --> 00:09:54,480
can see immediately 12 versus 14 minutes, 12 versus 18, 20 minutes. The gains aren't as much

95
00:09:54,480 --> 00:10:01,760
as like locally running the tests in seven seconds is a huge difference to 40 seconds, right? That's

96
00:10:01,760 --> 00:10:08,560
going to be a big difference to actually run them all. I think that's the main benefit we get out of

97
00:10:08,560 --> 00:10:17,840
this for our day-to-day work really. Well, you know what this means, don't you? No. We need to

98
00:10:17,840 --> 00:10:23,760
write more tests. Oh, we always need to write more tests. I was bracing for something terrible,

99
00:10:23,760 --> 00:10:31,280
but I'm totally on board with that. No, no, no. Yeah, we need literally double the test because

100
00:10:31,280 --> 00:10:36,320
we need something to keep us busy. What are we going to do all day if we can't run the test?

101
00:10:36,320 --> 00:10:41,120
No more excuses, skipping tests because they are using the database and might be slow.

102
00:10:41,120 --> 00:10:49,440
Exactly. Yeah, exactly. That's great. I'm happy to hear that. Yeah, it's a shame that the CI

103
00:10:49,440 --> 00:10:57,280
won't be quicker because that is so slow that it's a problem, but it's not an enormous problem,

104
00:10:57,280 --> 00:11:03,600
but I certainly don't ever wait for CI to run. I immediately go and start something else.

105
00:11:03,600 --> 00:11:10,640
Yeah, that's the pain. And I think when the Swift syntax being part of the tool chain will

106
00:11:10,640 --> 00:11:16,880
probably make the biggest difference there, that should shave a good five minutes off the build,

107
00:11:16,880 --> 00:11:23,280
I think, for the test. So I'm hopeful that we'll gain something. And then we hopefully end up

108
00:11:23,280 --> 00:11:32,640
under 10 minutes. That's my hope in the near term. And I'm going to slightly change the subject now,

109
00:11:32,640 --> 00:11:39,440
but that change to Swift syntax is what's needed to make macros also quicker to use and run, right?

110
00:11:39,440 --> 00:11:46,000
Yeah, exactly. Right now, as soon as you use a macro or use a package that uses macros,

111
00:11:46,000 --> 00:11:50,560
even if you don't use the macros, you end up, I think, even if you don't use macros,

112
00:11:50,560 --> 00:11:58,640
you end up building Swift syntax as part of your build. And that adds a lot to your normal builds

113
00:11:58,640 --> 00:12:05,440
and to your release builds as well. But that's a separate pipeline for us. So yeah,

114
00:12:05,440 --> 00:12:15,360
good savings there on the horizon. So one thing that struck me as I was

115
00:12:16,320 --> 00:12:21,680
searching for packages for this episode was I'm seeing more and more, and I've seen this for a

116
00:12:21,680 --> 00:12:27,360
little while now, but it really struck me today. I'm seeing more and more packages that say

117
00:12:27,360 --> 00:12:33,920
Swift package manager only, no installation instructions or anything like that for

118
00:12:33,920 --> 00:12:40,720
CocoaPods or Carthage or anything like that. And I mean, that's no huge surprise because

119
00:12:41,600 --> 00:12:48,880
it has, Swift package manager has now been around for, and usable within iOS and MacOS projects for

120
00:12:48,880 --> 00:12:54,960
several years, I think, well, more than five years now it's been in that state. And so you'd expect

121
00:12:54,960 --> 00:13:00,720
adoption of it to be to the point where people might not bother with CocoaPods. But I thought,

122
00:13:00,720 --> 00:13:06,720
as I kind of had this thought, I thought it might just be worth reminding people. I know we did talk

123
00:13:06,720 --> 00:13:13,600
about this kind of in the middle of last year when CocoaPods made an announcement of what their

124
00:13:13,600 --> 00:13:22,080
support and maintenance plans were. But I thought it would be useful just to recap that. And actually

125
00:13:22,080 --> 00:13:29,280
since we last talked about it, the CocoaPods team have put together a timeline

126
00:13:31,840 --> 00:13:38,640
for the shutdown of CocoaPods. So it's going to be no surprise to most people that CocoaPods will

127
00:13:38,640 --> 00:13:44,240
shut down at this point. I think the writing has been on the wall for quite a while that Swift

128
00:13:44,240 --> 00:13:49,840
package manager is going to be the way to include dependencies in any type of Swift project,

129
00:13:49,840 --> 00:13:57,200
whether it's server side or iOS or MacOS or whatever. And it's always good to know that

130
00:13:58,240 --> 00:14:07,120
timeline. So in January, so the basic timeline is this. In January of this year, an email went out

131
00:14:07,120 --> 00:14:11,440
to people who had contributed a pod spec. So anyone who, any package authors basically.

132
00:14:11,440 --> 00:14:17,840
And they're going to be, or they were informed that the CocoaPods repository was going to switch

133
00:14:17,840 --> 00:14:26,800
to read only with a link to the blog post. Nothing now happens until almost the end of next year.

134
00:14:27,600 --> 00:14:34,640
So September, October, 2026, another email will be sent out to everyone who has contributed a

135
00:14:34,640 --> 00:14:42,560
pod spec and saying that they basically have a month before a test run of going read only.

136
00:14:42,560 --> 00:14:51,120
Then in November, 2026, there is going to be this test run giving automation a chance to break

137
00:14:51,120 --> 00:14:59,120
early. So Autoserse, who's the person who wrote this post, Autothorox. And so he has a good test

138
00:14:59,120 --> 00:15:03,760
for everything to break and then it will be made not read only again. And then finally in December

139
00:15:03,760 --> 00:15:10,320
next year, CocoaPods will become read only and effectively shut down at that point. It will still

140
00:15:10,320 --> 00:15:16,720
be available. So none of your projects that use CocoaPods, if you have any left, are going to

141
00:15:16,720 --> 00:15:21,600
break because the pods will still be able to read, but you won't get any updates. You won't get any

142
00:15:21,600 --> 00:15:29,440
new packages or anything like that. So it's always worth being aware of this far in advance. And

143
00:15:29,440 --> 00:15:38,400
you've got at least 18 months if you do still use CocoaPods to basically make that switch to

144
00:15:38,400 --> 00:15:47,280
Swift Package Manager. Right. Remind me, I'm always unsure, you still need it actually on iOS,

145
00:15:47,280 --> 00:15:52,880
right? I mean, if you wanted any sort of package management, right? Other than

146
00:15:52,880 --> 00:16:03,760
pulling in libraries and- You need CocoaPods? Yeah, but on iOS, you can't, Swift PM can't build

147
00:16:03,760 --> 00:16:10,720
an iOS application from a package manifest. Ah, Swift PM as a build tool can't build it. No,

148
00:16:10,720 --> 00:16:21,600
that is correct. But Swift Package Manager through Xcode or Xcode build can. Right. Okay. Yeah. Got

149
00:16:21,600 --> 00:16:27,840
you. The package management part it can do, but it can't build the actual final product because-

150
00:16:27,840 --> 00:16:30,240
It can't do the actual build. No. No. You can't express it in the Swift

151
00:16:30,240 --> 00:16:35,040
Package Manifest, but Xcode obviously has a project file that can. Okay. Yeah.

152
00:16:35,040 --> 00:16:40,720
Exactly. And again, this is the kind of thing that will go away with the new build system,

153
00:16:40,720 --> 00:16:45,440
which is coming because it's a confusing situation that we're in at the moment where we have two

154
00:16:45,440 --> 00:16:51,280
build tools to different package management tools. It will be great to get all of this on a single,

155
00:16:51,280 --> 00:16:56,480
happy path through. If you would like to make an application, here is the build chain and

156
00:16:56,480 --> 00:17:01,040
dependency chain that you can use to do that. So I'm very much looking forward to that problem being

157
00:17:01,040 --> 00:17:08,480
solved. Yeah. Yeah. And to have a one-to-one answer, a one-to-one mapping, this is the command

158
00:17:08,480 --> 00:17:15,120
line you would use to do the exact same thing. And right now it kind of depends. There's two ways of

159
00:17:15,120 --> 00:17:22,000
doing it. And well, on iOS there isn't, but on macOS there kind of is for depending on what kind

160
00:17:22,000 --> 00:17:27,840
of thing, it's complicated. It is. And it shouldn't be, it really shouldn't be complicated.

161
00:17:27,840 --> 00:17:30,480
This bit should be the easy bit. Yeah.

162
00:17:30,480 --> 00:17:42,800
And I'm confident that we said this last time, but I'll reiterate it. I think the tremendous

163
00:17:42,800 --> 00:17:51,200
effort that went into CocoaPods over the years absolutely has to be acknowledged as an outstanding

164
00:17:51,200 --> 00:17:57,200
piece of open source work. They took what was an extremely difficult task. I mean, we think it's

165
00:17:57,200 --> 00:18:06,160
difficult now. Before CocoaPods, this was Git sub-modules and source code, and they took that

166
00:18:06,160 --> 00:18:16,560
process and made it just, well, possible for most people to do. And I think the way that project was

167
00:18:16,560 --> 00:18:23,920
run was always exemplary and the support that they gave. And I just can't say enough good things about

168
00:18:23,920 --> 00:18:29,600
the work that everybody who contributed to CocoaPods over the years did. Yeah. And quite

169
00:18:29,600 --> 00:18:36,400
a challenging thing also because the upstream can change under you, right? You're always sort of

170
00:18:36,400 --> 00:18:43,200
chasing new Xcode releases and stuff that might change in certain ways. And I find that super hard

171
00:18:44,240 --> 00:18:50,640
to have to track that. And I think that consistency with how they're maintaining this has

172
00:18:50,640 --> 00:18:56,240
gone right through to the shutdown plan, which is two years notice of a shutdown plan, which is

173
00:18:56,240 --> 00:19:02,320
fantastic. Yeah, absolutely. There's commercial products that get shut down faster, right?

174
00:19:02,320 --> 00:19:09,840
Exactly. I was going to say there can be so many products in the commercial world that just

175
00:19:09,840 --> 00:19:15,760
disappear one day. Maybe they lose their funding or they decide that it's no longer worth it. And

176
00:19:15,760 --> 00:19:25,200
then the next day they stop. Exactly. So, okay. So that's, yeah, just if you still have any

177
00:19:25,200 --> 00:19:31,120
projects that are on CocoaPods, make a plan to switch across to Package Manager. Yeah.

178
00:19:31,920 --> 00:19:41,920
Now's a good time. Right. There's another bit of news I saw on the social networks, a post on

179
00:19:41,920 --> 00:19:48,160
Mastodon about the value of open source software. I'm not sure if you've seen this one. It's a

180
00:19:48,160 --> 00:19:53,440
Harvard Business School and University of Toronto study to quantify the value of open source

181
00:19:53,440 --> 00:19:58,320
software. Did you catch that one by any chance? I didn't. No, that's news to me. Yeah.

182
00:19:59,040 --> 00:20:04,480
It's, I mean, this is, as you can imagine, an interesting question, but also a very difficult

183
00:20:04,480 --> 00:20:11,280
question to answer, right? What's the value of open source software? This study is attempting

184
00:20:11,280 --> 00:20:18,800
to quantify that. I've read the paper. It's as messy as you can imagine. There's a couple of

185
00:20:18,800 --> 00:20:24,560
different data sources that have been used to try and get a feel for how to answer those questions.

186
00:20:24,560 --> 00:20:32,800
I'm not sure how much to go into the details. It's an interesting read. I think it's probably

187
00:20:32,800 --> 00:20:39,600
best to focus on the takeaways. So their approximation is there's a $4.2 billion value in

188
00:20:39,600 --> 00:20:47,920
the initial value of the open source software and the value to the companies actually using it is

189
00:20:47,920 --> 00:20:53,520
actually much, much higher. So that's estimated at $8.8 trillion. Right. Of course. And the

190
00:20:53,520 --> 00:20:58,560
difference as you can imagine, so the way they actually determined the value, the created value

191
00:20:58,560 --> 00:21:03,920
is by doing a labor costs replacement. That was going to be my next question. Yeah.

192
00:21:03,920 --> 00:21:10,240
Yeah. So what they're effectively doing is they're looking at the lines of code in packages,

193
00:21:10,240 --> 00:21:19,440
and there's another study that sort of has established a formula. It's a big name for

194
00:21:19,440 --> 00:21:25,200
multiplying a few numbers together, but there's a formula to get to a value from a number of lines

195
00:21:25,200 --> 00:21:33,280
of code. And what that tries to do is estimate, well, if you had to reimplement that, how much

196
00:21:33,280 --> 00:21:39,360
would that cost? And that's the $4.2 billion. And it's worth noting that this is a pretty much

197
00:21:39,360 --> 00:21:45,200
a lower limit because they only looked at a smaller section and they left some open source software

198
00:21:45,920 --> 00:21:50,720
out of the picture entirely. Like for instance, open source operating systems, which are not

199
00:21:50,720 --> 00:21:55,840
insignificant, were not part of the study. So you can already see, you know, this is in fact,

200
00:21:55,840 --> 00:22:01,360
extremely significant. Yeah. Kind of. Right. I found that curious why that isn't part of it,

201
00:22:01,360 --> 00:22:07,120
but you know, it's, you have to start somewhere, I suppose. And you know, you have to, to, to at

202
00:22:07,120 --> 00:22:11,920
least do the bits that you know you can do. I guess that was perhaps the approach there.

203
00:22:13,520 --> 00:22:19,760
So they arrived at that. And then I, I don't know how they arrived at the multiplier,

204
00:22:19,760 --> 00:22:26,320
but effectively, so there's two ways of looking at the, the value created for the companies. One is

205
00:22:26,320 --> 00:22:32,560
if you lost all open source software and then it was recreated in an open source fashion, then,

206
00:22:32,560 --> 00:22:38,560
then it was, would be just $4.2 billion and everything would be as before. But obviously

207
00:22:38,560 --> 00:22:44,080
the multiplier comes in if every company had to actually re-implement all the open source software

208
00:22:44,080 --> 00:22:49,280
they're using again internally. Right. And then you can see like, obviously, you know, N companies

209
00:22:49,280 --> 00:22:55,440
doing the same thing will multiply that by N, you know, that's how you arrive at high

210
00:22:55,440 --> 00:23:05,200
multiplier from 4.2 billion to 8.8 trillion. So, so that's that. Yeah. I thought it was

211
00:23:05,200 --> 00:23:11,200
interesting, also interesting to see, and maybe as an incentive for companies to look at that and,

212
00:23:11,200 --> 00:23:17,680
and see, you know, be yet again, reminded of the value they're reaping from open source software.

213
00:23:17,680 --> 00:23:23,280
And that's, and that's the problem with, with any story like this is it just doesn't get enough

214
00:23:23,280 --> 00:23:30,160
publicity. So nobody who, nobody who would have the ability to pay any of that money will ever

215
00:23:30,160 --> 00:23:36,880
read that story. Yeah. Yeah. Yeah. I mean, absolutely. Right. Because it's free, right.

216
00:23:36,880 --> 00:23:42,720
It's, it's so hard to compete with free and it just doesn't well, and also it just doesn't even

217
00:23:42,720 --> 00:23:49,760
get discussed at, in that context. It doesn't, it doesn't enter the conversation in that context.

218
00:23:49,760 --> 00:23:54,640
Yeah. It, it seems like the paper hints at it in a couple of places that, that

219
00:23:57,280 --> 00:24:03,680
countries or economic blocks like the EU are getting increasingly aware that open source is

220
00:24:03,680 --> 00:24:09,680
kind of infrastructure and they are starting to fund open source and see it as a means of funding,

221
00:24:09,680 --> 00:24:15,840
because it's like building roads. It's like building like literal, like virtual roads that

222
00:24:15,840 --> 00:24:24,480
are used by just like roads help your companies move goods. So does open source software help

223
00:24:24,480 --> 00:24:31,520
companies move data and do their stuff, right? So there is a, there is a incentive for economic

224
00:24:31,520 --> 00:24:37,200
blocks to, to help fund open source software, perhaps at some point in the future, look at

225
00:24:37,200 --> 00:24:43,040
getting specific, like, you know, diverting taxes towards that, perhaps, you know, you can imagine

226
00:24:43,040 --> 00:24:48,960
all sorts of things where, where, you know, revenues or income in certain areas that get

227
00:24:48,960 --> 00:24:56,560
diverted or get, get put into pots of funding for these sort of programs to, to help. Because

228
00:24:56,560 --> 00:25:01,920
there's other interesting stats that came out of this, like 5% of open source authors create 96%

229
00:25:01,920 --> 00:25:06,560
of value, at least of the, of the parts that they looked at, which is extraordinary, right?

230
00:25:06,560 --> 00:25:15,360
That's, that's, that's an amazing figure. Of course. Yeah. And I mean, on the other hand,

231
00:25:15,360 --> 00:25:21,200
it's, it's probably, it's very tempting to, to shoot down a paper like that and say, well,

232
00:25:21,200 --> 00:25:24,560
I mean, how could that ever be correct? But I'm not even sure the point is to be

233
00:25:24,560 --> 00:25:31,440
super correct, right? You need to get a baseline, right? To make a point. Yeah. You, you, and you,

234
00:25:31,440 --> 00:25:37,280
you effectively, you, you underestimate. And I think that's really important that is, it is wrong,

235
00:25:37,280 --> 00:25:43,520
but it's going to be wrong in a good way, right? You're not, you're not overestimating the value,

236
00:25:43,520 --> 00:25:48,160
you're, you're clearly underestimating it. So you're, you're sort of setting a lower bound for

237
00:25:48,160 --> 00:25:54,240
what the value is, and that's already a tremendous value. So it gives you all incentive to, to, to,

238
00:25:54,240 --> 00:26:02,560
to help fund it further, right? To, to push this forward. So because there was a formula in there

239
00:26:02,560 --> 00:26:10,160
that sort of converted lines of code into effort, I, I of course took it as gospel and took our

240
00:26:10,160 --> 00:26:16,400
50,000 lines of Swift that we have in the Swift packet index. And apparently there's 18 person

241
00:26:16,400 --> 00:26:22,240
years of effort in it. Now, given that Swift packet index is five years old, and we're two

242
00:26:22,240 --> 00:26:26,800
people, that's 10 person years. That basically means we're two ex-programmers, right? We are,

243
00:26:26,800 --> 00:26:34,480
we are, we've, we've, we've beat the, we've beat the algorithm there.

244
00:26:34,480 --> 00:26:39,040
What I was going to wrap it up with, but I mean, you've made the perfect,

245
00:26:39,040 --> 00:26:43,280
the perfect ending there. What I was going to wrap it up with was if we write more tests,

246
00:26:43,280 --> 00:26:48,320
we get, we're worth more money, right? Oh, absolutely. That's, that's the other thing.

247
00:26:48,320 --> 00:26:56,640
And also now that you have a formula that converts lines of code into effort, you know,

248
00:26:56,640 --> 00:27:02,880
how you often asked, well, how long is this going to take? Now you don't have to, you don't have to

249
00:27:02,880 --> 00:27:07,680
answer how long it's going to take. All you need to figure out is how many lines of code will you

250
00:27:07,680 --> 00:27:12,160
need? And then you can convert back. That's amazing. Estimating will become so much easier.

251
00:27:12,160 --> 00:27:21,360
It's amazing. Easy. Yeah. Easy. Easy. Fantastic. Shall we move on to some packages?

252
00:27:21,360 --> 00:27:27,840
Let's do it. Do you want to go first? I shall do so. My first package is called

253
00:27:27,840 --> 00:27:33,520
just codable. I mean, codable, not just codable, but you know what I mean? It's by,

254
00:27:34,160 --> 00:27:43,680
by Andrey Chernenko. And I've seen a few of these. It's using macros to help you implement,

255
00:27:43,680 --> 00:27:48,240
help you with a codable implementation. I think we've even talked about a couple of,

256
00:27:48,240 --> 00:27:54,160
of these in the past. The reason I wanted to mention this one in particular is that I saw it

257
00:27:54,160 --> 00:28:02,160
has a @memberwise initializable macro as well. And I thought that's, that's not something I'd seen

258
00:28:02,160 --> 00:28:09,120
before that probably exists. I just hadn't noticed it. But what that does is it allows you to tack

259
00:28:09,120 --> 00:28:18,240
that onto a struct and then it will like in auto-generate the public, the initializer.

260
00:28:18,240 --> 00:28:23,440
And why, the reason why that's nice is because you can then just tack public in front of it.

261
00:28:23,440 --> 00:28:29,200
And then you don't have to, you know, like generate it yourself and maintain it in particular. Right.

262
00:28:29,200 --> 00:28:35,760
You know how often it's, it's really tedious to have a struct that suddenly needs to be public

263
00:28:35,760 --> 00:28:41,920
because then you need to, that initializer. And that is then fiddly because yes, you can

264
00:28:41,920 --> 00:28:49,440
auto-generate it once with the Xcode refactor tool, but that does it once. If you add properties,

265
00:28:49,440 --> 00:28:53,680
then you need to go in and add the properties. And this is nice because you can just tack this on

266
00:28:54,320 --> 00:28:58,240
and you don't have to do anything with it. It'll, it'll always be in sync with your,

267
00:28:58,240 --> 00:29:03,840
with your properties. So I think that's even, that would be even nice to just have as a standalone

268
00:29:03,840 --> 00:29:12,800
package. And I also went to, wanted to mention while talking about this, there's a, currently a

269
00:29:12,800 --> 00:29:19,840
Swift forums thread about the future of serialization and deserialization, which talks specifically

270
00:29:19,840 --> 00:29:25,680
about using macros to, to do effectively what this package is doing. So this is on the horizon to

271
00:29:25,680 --> 00:29:33,840
become a, a proper, you know, foundation, I guess, or at least Swift, Swift standard library thing

272
00:29:33,840 --> 00:29:41,600
at some point. So the, the codable thing will, will sort of transition at some point,

273
00:29:41,600 --> 00:29:47,040
all these packages will sort of be, I guess, Sherlocked by the standard library. But this

274
00:29:47,040 --> 00:29:52,880
memberwise, memberwise initializable thing is definitely a nice thing that might then survive

275
00:29:52,880 --> 00:29:59,840
in a separate package. - Sure. So do you know how well I, I know you? I spotted that package.

276
00:29:59,840 --> 00:30:04,080
I thought that sounds interesting. And then I thought, Spencer's probably going to pick that.

277
00:30:04,080 --> 00:30:07,840
So I, I put it on my backup list. - Thank you.

278
00:30:07,840 --> 00:30:21,840
- You didn't let me down. My first one is a package called Aesthetic Text by Kyle Bashore.

279
00:30:21,840 --> 00:30:26,000
And I talked about this a little bit in iOS Dev Weekly, but I thought it was mentioning,

280
00:30:26,000 --> 00:30:29,600
worth mentioning here too, because there's a couple of interesting things about it. First of all,

281
00:30:29,600 --> 00:30:37,280
I think the package itself is great. What it does is it's, is it's a Swift UI package. It's a view

282
00:30:37,280 --> 00:30:42,720
modifier that you can add to any Swift UI view. And you just tack on the end of any text view,

283
00:30:42,720 --> 00:30:51,120
the view modifier Aesthetic Text. And what it will do is it will very cleverly work out where

284
00:30:51,120 --> 00:30:58,880
the line breaks should go in any text that was passed to it. So that each line of the text

285
00:30:58,880 --> 00:31:04,160
is approximately the same length. So you never get one line that's very long followed by a line

286
00:31:04,160 --> 00:31:11,600
that's very short, which is a common UI problem, especially when it comes to text that is centered

287
00:31:11,600 --> 00:31:17,360
in the middle of a view. So if you have like a empty state view or something that's, that's,

288
00:31:17,360 --> 00:31:24,320
that has some centered text in the middle of the view, having different length lines looks really

289
00:31:24,320 --> 00:31:31,120
awkward. So that's what the package does. And I think, first of all, I think that's,

290
00:31:31,120 --> 00:31:39,120
that's just a really great little tool that you can use to add polish to your apps before you

291
00:31:39,120 --> 00:31:45,200
release them. But also, and in relation to my next package, which we'll talk about in a minute,

292
00:31:45,200 --> 00:31:56,400
this is a great example to me of a package that is UI based, which a lot of people are wary of

293
00:31:56,400 --> 00:32:02,160
adding UI based packages, but it's the kind of package that if it ever did get abandoned,

294
00:32:02,160 --> 00:32:07,440
or if it ever, if anything did happen to it so that you couldn't use this package anymore,

295
00:32:07,440 --> 00:32:12,640
removing a package like this would be super easy because all it would do is put the text back on

296
00:32:12,640 --> 00:32:17,040
to uneven lines, which is slightly less polished, but not the end of the world.

297
00:32:17,040 --> 00:32:21,440
And I thought it was, it was worth mentioning for both those reasons.

298
00:32:21,440 --> 00:32:27,920
- I think where this is also great is in a localizable context, because you know,

299
00:32:27,920 --> 00:32:33,840
you might do the proper line breaks in your primary language and then forget about all the

300
00:32:33,840 --> 00:32:38,160
other languages that have potentially quite different word lengths and stuff and, you know,

301
00:32:38,160 --> 00:32:40,480
sentence structure and have quite different layouts.

302
00:32:40,480 --> 00:32:45,840
- Yeah, of course. Yeah, that's great. That's a great point. Really great point. So yeah,

303
00:32:45,840 --> 00:32:53,600
that's Aesthetic Text by Kyle Bashaw and it looks great. There's also a great screenshot in the,

304
00:32:53,600 --> 00:32:58,160
I love people who put a good screenshot or even a video in their readme files.

305
00:32:58,160 --> 00:33:06,800
It's always good to know. I quite often, when I'm looking through package readmes for preparing for

306
00:33:06,800 --> 00:33:12,880
this podcast, if a UI library or something related to the UI doesn't have any kind of screenshot of

307
00:33:12,880 --> 00:33:16,560
it, I'm afraid, rather than pursue it, I just close the tab.

308
00:33:16,560 --> 00:33:23,920
- Well, you'll be happy to know that this is the fourth on my list and I put it fourth because I

309
00:33:23,920 --> 00:33:26,320
kind of expected you to talk about it.

310
00:33:26,320 --> 00:33:27,440
- Because you knew I would pick it.

311
00:33:27,440 --> 00:33:35,120
Well, I'm glad we're both living up to our reputations.

312
00:33:35,120 --> 00:33:38,880
- Yeah, there we go. We have nicely segmented into a certain package picks.

313
00:33:38,880 --> 00:33:41,200
- Exactly, yeah.

314
00:33:41,200 --> 00:33:47,280
- Although we might have a conflict coming up. Let's see. I sort of suspect we have one that we

315
00:33:47,280 --> 00:33:47,520
both...

316
00:33:47,520 --> 00:33:50,560
- Setting up a suspenseful finish. Yeah, exactly.

317
00:33:50,560 --> 00:33:57,920
- I don't think the second one falls in that category. That's called RT-SAN Standalone

318
00:33:57,920 --> 00:34:06,400
Swift by Joseph Kawa. So this one is a bit difficult to talk about. So I think this is

319
00:34:06,400 --> 00:34:16,240
actually a Vervesoft software pick, one of Dave's picks. In all likelihood, you won't need this

320
00:34:16,240 --> 00:34:25,760
package. Very few people will. It's a fascinating package though. So this is a package that uses a

321
00:34:25,760 --> 00:34:33,440
feature of Clang and the feature is the real-time sanitizer. Now, most people will probably be

322
00:34:33,440 --> 00:34:41,440
aware of the sanitizers in Clang that we use commonly like T-SAN, which is the thread sanitizer

323
00:34:41,440 --> 00:34:50,240
and ASAN, the address sanitizer. And what these two do is thread sanitizer to highlight cases

324
00:34:50,240 --> 00:34:56,160
where you might have race conditions and stuff like that. Concurrent access to shared data

325
00:34:56,160 --> 00:35:06,320
structures and ASAN is where you write over address space, stuff like that. And this is

326
00:35:06,320 --> 00:35:13,520
sort of similar. It uses a real-time sanitizer feature of Clang where

327
00:35:13,520 --> 00:35:21,520
methods or functions have been annotated with a non-blocking attribute in Clang. And what that

328
00:35:21,520 --> 00:35:29,840
means is that they have a deterministic execution time guarantee. So they will return

329
00:35:29,840 --> 00:35:36,560
deterministically. They won't just hang. And what that means is if you annotate,

330
00:35:36,560 --> 00:35:46,000
this package comes with a binary library and a macro, which you can add to a function of

331
00:35:46,000 --> 00:35:52,560
yours. So you can say add non-blocking as a annotation. And then what you're expressing

332
00:35:52,560 --> 00:36:02,640
there is you don't want this function to call any blocking API. And blocking can be stuff you don't

333
00:36:02,640 --> 00:36:06,800
actually... I mean, when I saw the example, print, print is actually in that context, a potentially

334
00:36:06,800 --> 00:36:10,720
blocking API because under the hood, it might actually hang because certain buffers aren't

335
00:36:11,360 --> 00:36:16,480
a fool or something. There's no guarantee on the operating system level that print will actually

336
00:36:16,480 --> 00:36:22,640
return deterministically. And there's lots of malloc and stuff like that, all functions like

337
00:36:22,640 --> 00:36:28,720
that. So if you have code that sort of needs to have certain real-time guarantees, needs to finish

338
00:36:28,720 --> 00:36:37,120
in a certain deterministic execution time, you can tack this on. It'll give you runtime. You can

339
00:36:37,120 --> 00:36:43,360
configure what it'll do. It'll either trap and give you a printout or it'll run and at the end,

340
00:36:43,360 --> 00:36:48,640
give you a summary of cases where you've actually called API that didn't meet that guarantee.

341
00:36:48,640 --> 00:36:54,800
I found that really fascinating that you can expose that sort of stuff on the Swift level

342
00:36:54,800 --> 00:37:00,240
and then use it at such a high level and get the feedback from Clang. And I found that really

343
00:37:00,240 --> 00:37:05,760
interesting. And I guess it's also a look into the future, what might be possible with other

344
00:37:06,320 --> 00:37:10,960
instrumentation like that, that you can then expose in macros, give you really powerful tools

345
00:37:10,960 --> 00:37:17,920
to say, "Right, this is a critical section in my app. I can't have this block." I can think of lots

346
00:37:17,920 --> 00:37:21,840
of industries where that's super interesting. There's obviously other programming languages

347
00:37:21,840 --> 00:37:25,760
that have that as a feature. There's real-time operating systems that have that as a feature.

348
00:37:25,760 --> 00:37:33,520
I mean, this is a part of that story, Swift as a really powerful and safe language. And

349
00:37:33,520 --> 00:37:39,120
you can sort of, I guess, in certain ways, get part of that feature set and pull it into

350
00:37:39,120 --> 00:37:44,240
a Swift application via that route. Super interesting, I thought.

351
00:37:44,240 --> 00:37:54,000
- Yeah, absolutely. Fascinating. My next package is in contrast to my previous package,

352
00:37:54,000 --> 00:37:58,240
which was the one that I said, it's the kind of thing which is kind of easy to remove if you ever

353
00:37:58,240 --> 00:38:08,880
need to. This one is a package that I really like the look of. It's called Swift File by Pelagornis.

354
00:38:08,880 --> 00:38:19,120
And it's basically a refinement of the API in Swift for accessing, creating, writing to, reading

355
00:38:19,120 --> 00:38:26,960
from files on disk. And I've always found those APIs a little awkward to use, both in Swift and

356
00:38:26,960 --> 00:38:34,080
in Objective-C, because they're ultimately calling the same functions. And I know they have got much

357
00:38:34,080 --> 00:38:44,880
better since the adoption of Swift, but I still find them a little clunky to use in certain cases.

358
00:38:44,880 --> 00:38:56,320
And what this does is it gives a real streamlined, super concise API to create files. So

359
00:38:56,880 --> 00:39:01,360
you can, for example, call a create file function. You can call file.write. You can,

360
00:39:01,360 --> 00:39:05,440
you know, path.temporary will give you the temporary folder and things like that.

361
00:39:05,440 --> 00:39:13,920
So you can really streamline that API code. And I really like that. And I hope that one day

362
00:39:13,920 --> 00:39:20,640
Swift and the standard library tweak those APIs, as we all hope that all APIs get easier to use,

363
00:39:20,640 --> 00:39:26,160
right? There's no pressing problem there, but it's the kind of thing that could of course be improved.

364
00:39:26,640 --> 00:39:34,080
But this package, I would be really hesitant to place at the core of any app, because if you're

365
00:39:34,080 --> 00:39:40,240
doing a lot of file management stuff, it's likely that that code is going to seep into your

366
00:39:40,240 --> 00:39:47,440
application in a way that might be difficult to remove in the future. So I really like this

367
00:39:47,440 --> 00:39:55,360
package. I think it's kind of interesting to see what this API might look like, but it's one of my

368
00:39:55,360 --> 00:39:59,680
traditional, it's good, but I maybe wouldn't recommend it packages.

369
00:39:59,680 --> 00:40:05,920
Actually, yeah, we didn't actually have a conflict. You didn't pick the one I thought

370
00:40:05,920 --> 00:40:10,240
you might pick. I've still got one more. I've still got one more. Oh, you still have one.

371
00:40:10,240 --> 00:40:13,760
All right. But I, oh, right. I started this time around, so I might still.

372
00:40:13,760 --> 00:40:19,760
But I'm fairly confident that it's not going to be, my last one is not going to be.

373
00:40:20,560 --> 00:40:29,760
Okay. Well, let's see. So my third and final pick is RenderMeThis by JustEther. That's the username

374
00:40:29,760 --> 00:40:37,040
associated with the package. It was on my short list and my package is by the same author.

375
00:40:37,040 --> 00:40:45,120
Oh, there you go. Fantastic. Nice. So this is a really interesting package. Do you know how SwiftUI

376
00:40:45,840 --> 00:40:51,440
is magical in that it detects what has changed in a view model and then updates the UI bits that

377
00:40:51,440 --> 00:40:57,280
are affected? Absolutely. Yeah. And you just see it happen, right? You see the UI update and you

378
00:40:57,280 --> 00:41:03,200
think, oh, that's fantastic. The problem is it's quite easy to accidentally oversubscribe and then

379
00:41:03,200 --> 00:41:08,400
trigger way more changes than required. And that's not something you necessarily easily see because

380
00:41:08,400 --> 00:41:15,040
it might just re-render and it doesn't look that different. And this package is a great tool because

381
00:41:15,040 --> 00:41:23,920
it provides both a view builder wrapper as well as a view modifier to render check a view hierarchy.

382
00:41:23,920 --> 00:41:31,600
And by render check, I mean, is it will sort of flash in red the parts of the UI that have just

383
00:41:31,600 --> 00:41:38,000
updated and sort of flashes red and then gets translucent. It's a really nice effect. If you

384
00:41:38,000 --> 00:41:43,920
look at the ReadMe, there's a video showing it in action. It's really great. I mean, if you look at

385
00:41:43,920 --> 00:41:48,560
the ReadMe, you know exactly what it does immediately just by looking at it. And it's so

386
00:41:48,560 --> 00:41:53,920
easy to add, right? You just tack on a view modifier or just wrap it in a view builder if you

387
00:41:53,920 --> 00:42:02,480
prefer to do it that way. And then it just works. It does its thing and shows you what actually is

388
00:42:02,480 --> 00:42:07,200
happening. And you can still interact with your app, right? So you can just, in the example,

389
00:42:07,200 --> 00:42:12,560
there's moving a slider and then you see a label is changing and obviously the slider is changing

390
00:42:12,560 --> 00:42:19,600
itself. But you see all the parts of the app that might be affected by the slider at a glance. So I

391
00:42:19,600 --> 00:42:28,000
thought that was really great. Yeah. Well, I can say it was on my shortlist. And I think both of

392
00:42:28,000 --> 00:42:33,520
the packages, because my next package is by the same author, as I said, have great ReadMe files.

393
00:42:33,520 --> 00:42:42,400
Both of them jumped out at me in terms of potential to talk about. And so that's always a good sign.

394
00:42:43,360 --> 00:42:46,720
The package I'm going to talk about though is called GlowGetter,

395
00:42:46,720 --> 00:42:57,440
G-L-O-W-Getter. And it is also a SwiftUI package. But it does something really interesting. It

396
00:42:57,440 --> 00:43:06,560
kind of enables the HDR mode on your phone, or I guess on your Mac, if you have a HDR compatible

397
00:43:06,560 --> 00:43:15,120
display on your Mac, and allows views in your SwiftUI view hierarchy to become brighter than

398
00:43:15,120 --> 00:43:22,880
they would normally be able to with standard APIs. So normally the UI that you create can only become

399
00:43:22,880 --> 00:43:31,360
as bright as the screen is currently set to. But if you're doing an HDR image, like if you go into

400
00:43:31,360 --> 00:43:38,800
the Photos app, you'll see your phone screen quite visibly brighten when you show a picture that was

401
00:43:38,800 --> 00:43:46,320
taken with HDR information in it. And what this allows you to do is apply that as a view modifier

402
00:43:46,320 --> 00:43:52,880
to any view with a .glow view modifier. And the strength of the parameter that you pass to that

403
00:43:52,880 --> 00:44:00,320
modifier determines how much it's going to glow and kind of stand out. And I've never seen anything

404
00:44:00,960 --> 00:44:06,560
like that in terms of SwiftUI view modifiers, but I have seen an app which is kind of similar.

405
00:44:06,560 --> 00:44:12,720
And in fact, at the bottom of the README file for Glow Getter, there is an acknowledgement to

406
00:44:12,720 --> 00:44:20,640
Jordi Bruin and Ben Haraway, who wrote an app called Vivid, which allows you to take your MacBook

407
00:44:20,640 --> 00:44:27,840
HDR display screen and max the brightness even beyond what is kind of standard for the screen.

408
00:44:27,840 --> 00:44:35,520
And it does that by enabling HDR mode and then using that to then increase the brightness.

409
00:44:35,520 --> 00:44:42,000
And it says that this repository, the Glow Getter repository, adapts some code that was built for

410
00:44:42,000 --> 00:44:49,600
Vivid that lets you use your MacBook at the maximum brightness. So I thought it was kind of an

411
00:44:49,600 --> 00:44:54,960
interesting package in terms of I've never seen anybody bring this into an environment where it

412
00:44:54,960 --> 00:45:00,000
would be this easy to apply to something. - Nice. Beautiful icon too in the README,

413
00:45:00,000 --> 00:45:02,480
the Glow Getter icon. - Yeah, really. Yeah.

414
00:45:02,480 --> 00:45:12,640
So I think it's kind of unheard of actually that we have both picked a package by the same author

415
00:45:12,640 --> 00:45:24,080
in the same episode. And back-to-back packages by the same author. So Aether is truly the star of

416
00:45:24,080 --> 00:45:26,000
this episode. - Absolutely.

417
00:45:26,000 --> 00:45:35,360
- Great. Well, let's wrap it up there then, shall we? This is, I think this is actually the first

418
00:45:35,360 --> 00:45:41,440
one in a little while that's been bang on schedule. So we are running exactly to our

419
00:45:41,440 --> 00:45:44,080
self-imposed clock. - Back on track.

420
00:45:44,080 --> 00:45:50,720
- Unlike normal. Back on track. Yeah. So you should expect, here's me confidently saying,

421
00:45:50,720 --> 00:45:57,280
you should expect another episode in three weeks. Let's be confident about it. But until then,

422
00:45:57,280 --> 00:46:01,040
we will say goodbye for now. And I will see you next time.

423
00:46:01,040 --> 00:46:03,520
- See you next time. Bye-bye. - All right. Bye-bye.