WEBVTT 00:00.000 --> 00:10.480 OK, let's get started. 00:10.480 --> 00:14.240 Building a minimal terminal UI library for language 00:14.240 --> 00:17.240 Lua, but it's close platform. 00:17.240 --> 00:20.800 So it probably would, if anyone wants to build something 00:20.800 --> 00:26.280 like this, might be interesting to see what we ran into. 00:26.280 --> 00:28.600 Small shout out to LabLua and G-Sup for helping 00:28.600 --> 00:34.720 us out funding the project, which really helped us last summer. 00:34.720 --> 00:37.400 In the set for a sake of time, let's skip on that. 00:37.400 --> 00:40.680 I can show it demo of what it does today. 00:47.160 --> 00:52.520 So this is Lua Rocks, which is the CLI interface for a package 00:52.520 --> 00:56.360 manager, and we simply use it as an example. 00:56.360 --> 01:01.720 Like, can we wrap it in something useful in a text-based UI? 01:01.720 --> 01:05.800 So the first thing we did is we created some widgets on the command line. 01:05.800 --> 01:07.640 So it's at the command line becomes interactive. 01:07.640 --> 01:15.640 So it can actually walk around down execute commands and exit in this case. 01:15.640 --> 01:21.320 The second example we did is full screen application. 01:21.320 --> 01:27.560 Well, we have panels, we have a header bar, a keyboard shortcuts, 01:27.560 --> 01:31.400 we can move around, we can hike panels, show panels. 01:31.400 --> 01:34.280 So this is the current state of what it does. 01:38.840 --> 01:41.720 In fact, the presentation. 01:41.720 --> 01:45.560 So the goals, cross-platform, it had to work. 01:45.560 --> 01:49.640 The idea is mainly from when you start working with Lua, 01:49.640 --> 01:52.120 Lua's source distributed, which is hard on windows. 01:52.120 --> 01:56.520 So can we have a novice user built something of just 01:56.520 --> 01:57.640 works across the board? 01:57.640 --> 02:00.920 Just a basic application to get people started. 02:01.960 --> 02:06.360 Non-blocking input, Lua uses cover-teens. 02:06.360 --> 02:09.800 So non-blocking input is something that we really wanted. 02:11.160 --> 02:16.760 And no external dependencies to make installation easier. 02:16.760 --> 02:22.920 Non-bills explicitly, no mouse support, no overlapping windows and this kind of stuff. 02:22.920 --> 02:24.760 So what does it look like? 02:24.760 --> 02:25.880 It's the operating system. 02:25.880 --> 02:26.920 And there's two libraries. 02:26.920 --> 02:29.320 The first one is Lua's system, which is only providing 02:29.320 --> 02:32.040 C code with interfaces to the operating system. 02:32.040 --> 02:35.320 And the second one is the one which has far more code, 02:35.320 --> 02:38.360 that actually does all the drawing and everything else. 02:38.360 --> 02:40.840 But the good thing is that you're already in the target language. 02:40.840 --> 02:43.560 So you can write everything in the target language. 02:43.640 --> 02:47.240 We wanted Lua's system to be independent of platform, 02:47.240 --> 02:49.320 but because terminals are so fundamentally different 02:49.320 --> 02:52.200 on both systems and physics and windows, 02:52.200 --> 02:55.800 some details do leak through the terminal that Lua library. 02:58.680 --> 02:59.720 The easy part. 02:59.720 --> 03:02.280 To get this done, I'll see support. 03:02.280 --> 03:03.960 Windows gained it in 2019. 03:05.480 --> 03:08.120 That was like the big thing that made it viable, 03:08.120 --> 03:11.560 otherwise it just wouldn't have worked in my opinion. 03:11.880 --> 03:14.120 And getting a terminal size, this is a single API call, 03:14.120 --> 03:15.560 it's an old platforms. 03:15.560 --> 03:16.920 Everything else was a bit harder. 03:18.760 --> 03:23.400 The biggest challenge by far was the ambiguous with, 03:23.400 --> 03:25.880 display with, or display with in general. 03:25.880 --> 03:28.920 Windows does not have for UGFA or Unicode characters. 03:29.720 --> 03:32.200 Like emojis, which are two columns wide on the text screen. 03:33.080 --> 03:35.480 Chinese, East Asian languages, you have a lot of 03:35.480 --> 03:37.720 characters, two columns wide. 03:37.720 --> 03:40.120 And for everything you draw on screen, 03:40.200 --> 03:42.680 you just don't want stuff to mess up your, 03:42.680 --> 03:44.200 display your construction, whatever you have. 03:46.760 --> 03:49.000 On Windows was fairly easy to do the output, 03:49.000 --> 03:51.800 but on Windows the hard part was like doing the input proper. 03:55.240 --> 03:58.120 So to handle this, we had to implement, 03:58.120 --> 04:01.720 use the by market school and implementation 04:01.720 --> 04:02.760 that wasn't available on Windows. 04:04.360 --> 04:06.760 And for the width checking, we had to 04:06.760 --> 04:09.720 create functions for character and strings, which is, 04:09.720 --> 04:10.760 like a very simple. 04:10.760 --> 04:13.720 And we had to implement a string.sub, which is the 04:13.720 --> 04:16.440 lower method for string mangling, cutting 04:16.440 --> 04:17.800 and pieces and rearranging. 04:18.840 --> 04:21.640 And we had to write two, one for UGFA characters, 04:21.640 --> 04:23.240 because it was a bit based. 04:24.440 --> 04:26.840 And the second one is the same thing, 04:26.840 --> 04:28.920 but then based on display columns. 04:28.920 --> 04:31.480 Because everything you do in your UI is basically based on the columns, 04:31.480 --> 04:33.880 you display and not on bytes or individual characters. 04:34.840 --> 04:36.840 This was the hard part. 04:36.840 --> 04:38.760 On top of that, we built, 04:38.760 --> 04:42.760 it goes input is one by by at a time from the keyboard. 04:44.600 --> 04:47.000 And it meant that we had to rebuild a line editor. 04:48.120 --> 04:52.680 We built this thing and it turned out this thing actually became like, 04:52.680 --> 04:56.200 the core of all text handling for everything we displayed, 04:56.200 --> 05:00.920 because it has these methods to check with columns everything. 05:01.880 --> 05:06.360 And so this thing, we wrote as a line editor, 05:06.360 --> 05:09.000 edges is all over the place in everything we do. 05:11.640 --> 05:14.440 The next picture challenge was non-blocking input. 05:14.440 --> 05:16.360 On post-6 it's fairly standard. 05:16.360 --> 05:18.600 You said it's a non-blocking, you read one by at the time, 05:18.600 --> 05:19.960 canonical mode and the whole shabang. 05:21.240 --> 05:23.960 When I wind those, there just is no such thing. 05:24.600 --> 05:28.440 So we fell back on the very old conno.h, 05:28.600 --> 05:31.240 which is like, I think somewhere from the 80s, 05:31.240 --> 05:33.000 but it's still available, it still works everywhere. 05:34.120 --> 05:36.040 And they can read directly from the keyboard buffer. 05:38.040 --> 05:40.840 Which means that on post-6, we can just read the single byte and on Windows, 05:40.840 --> 05:42.200 we have to go through a bunch of hoops. 05:43.960 --> 05:46.040 The main issue is that on Windows, 05:46.760 --> 05:49.400 you read a white character, you want to output a single byte. 05:50.680 --> 05:54.520 So we need to implement a buffer to store whatever the remainder is of the character that we're reading. 05:55.480 --> 05:58.680 And there's a bit of a thing with Windows that 05:58.680 --> 06:02.760 occasionally leaks, keyboard scan codes, raw codes, 06:02.760 --> 06:05.480 through the interface, you want to ask me why. 06:05.480 --> 06:08.200 Turn more scan, keep up, and you have to catch that. 06:08.200 --> 06:11.640 But that's one general way to go up against, it's easy to fix, 06:11.640 --> 06:14.440 but figuring out why the weird bytes are just coming in, 06:14.440 --> 06:15.320 that's the hard part. 06:18.520 --> 06:21.640 In the tellerization, this is a bit tricky on Windows, 06:21.800 --> 06:24.680 over in post-6 actually on Windows, it was pretty easy. 06:24.680 --> 06:30.280 But on post-6, it's the usual canonical echo and setting it to a non-blocking. 06:31.720 --> 06:36.600 But what turned out on, especially on Macs, 06:36.600 --> 06:40.680 it seems that if you set standard in to non-blocking, 06:40.680 --> 06:43.240 you're also setting standard out to non-blocking, 06:43.240 --> 06:44.680 and that's something you don't want. 06:44.680 --> 06:47.240 And the reason is that you have a file descriptor, 06:47.880 --> 06:50.440 but they can point to the same file description. 06:50.520 --> 06:53.880 If you set it to non-blocking, you modify the file description, 06:54.520 --> 06:56.520 and then the other one that points to the same description, 06:56.520 --> 06:57.480 also becomes non-blocking. 06:58.200 --> 07:01.000 So that's where we had another one of those, 07:01.000 --> 07:02.040 what the heck is happening here. 07:03.080 --> 07:07.400 Once you know, it's easy to fix, you make sure that each file descriptor 07:07.400 --> 07:10.200 gets its own file description, and then they're independent, 07:10.200 --> 07:11.160 then you can do whatever you want. 07:14.520 --> 07:18.280 So if you have individual bytes for keyboard reading coming in, 07:18.440 --> 07:21.800 then the first thing we do on the lowest level, 07:22.680 --> 07:24.200 like the three functions you see here, 07:24.200 --> 07:26.360 the top one is the C implementation that you showed. 07:27.400 --> 07:30.200 The Riki is actually the one where we immediately 07:30.200 --> 07:32.520 power this in a timeout and a sleep function. 07:34.680 --> 07:38.280 The reason we do it is because if we change the underlying 07:38.280 --> 07:39.640 implementation of the sleep function, 07:39.640 --> 07:42.520 to something non-blocking, if you use a coroutine scheduler, 07:42.520 --> 07:46.280 or whatever you have in the language that you're using 07:46.360 --> 07:49.000 for a lure that is co-routines, 07:51.000 --> 07:54.120 then it means that everything beyond it just becomes non-blocking. 07:55.640 --> 07:58.600 And the last function is actually the one that does 07:58.600 --> 08:01.640 all the extraction for parsing-answer codes and everything else. 08:02.680 --> 08:07.640 To give you a quick idea of what it looks like. 08:07.640 --> 08:09.880 So if you have a simple prompt in a terminal, 08:10.440 --> 08:12.840 you can see the double with characters, 08:12.920 --> 08:15.240 like the two Chinese characters of the ball with, 08:15.240 --> 08:17.800 as is the rocket. 08:20.280 --> 08:27.800 You can, well, you can copy paste some stuff, just works. 08:29.320 --> 08:33.000 But if you place the sleep method that we have in the input, 08:35.800 --> 08:38.120 then this is the second example, exact same code. 08:38.760 --> 08:41.880 Other than we replace the sleep function with something 08:41.880 --> 08:43.640 that deals with an algorithm in the top right and corner, 08:43.640 --> 08:45.400 we have a clock ticking, and despite it, 08:45.400 --> 08:49.480 I'm usually, if I would use the standard C functions from the C library 08:49.480 --> 08:51.320 to read input, it would be blocking. 08:51.320 --> 08:54.440 But in this case, we can do background work, timers, whatever. 08:59.880 --> 09:01.560 So from there, 09:03.960 --> 09:07.080 another thing to never ran into was spurring a slow initially, 09:07.080 --> 09:10.040 we tried, when we want to, if you draw some kind of skin, 09:10.120 --> 09:13.320 screen, you want to reset the state to what it was before you started drawing. 09:14.360 --> 09:17.640 So you can create a terminal for foreground colors, background colors, 09:17.640 --> 09:20.040 cursor position, but it turns out to be really slow. 09:21.080 --> 09:25.640 The reason it's slow is that when you write a sequence to the terminal, 09:26.680 --> 09:29.240 immediately read, the answer won't be there yet. 09:30.040 --> 09:32.440 Terminal needs processing time, so you have to do a sleep, 09:32.840 --> 09:37.000 and then you run into the underlying decision of the operating system, 09:37.640 --> 09:41.160 and a typically is anywhere between two and 15 milliseconds. 09:41.160 --> 09:44.440 So if you do it once on drawing a screen, not a problem. 09:44.440 --> 09:46.200 Typically, you create widgets. 09:46.200 --> 09:49.000 Every widget does the same thing like getting state, 09:49.000 --> 09:49.960 we store it afterwards. 09:50.920 --> 09:57.000 And if you do that, then it quickly adds up to 150 milliseconds, 09:57.000 --> 09:59.000 and then you will start noticing it and it comes sluggish. 10:00.280 --> 10:02.920 The way we work around this is actually by tracking state, 10:03.160 --> 10:07.240 to create its stacks in which we push the state, 10:07.240 --> 10:10.280 and then pop the state without actually querying the terminal. 10:10.280 --> 10:12.120 And that makes it really snappy. 10:13.400 --> 10:17.160 I use a number of items, the cursor shape, visibility, 10:17.160 --> 10:19.880 scroll region, text attributes that we create the stacks for. 10:20.600 --> 10:22.600 And they look pretty simple like this. 10:23.880 --> 10:27.880 You push in the red foreground color, and it's at brightness, 10:27.880 --> 10:29.400 everything else stays the same. 10:30.280 --> 10:32.120 You write a text, does it? 10:32.120 --> 10:34.680 Then you pop it, and then you restore it to the previous. 10:34.680 --> 10:37.800 Whatever the color was, there was on the previous stack slot, basically. 10:39.000 --> 10:41.560 This worked really nicely for all of those things 10:41.560 --> 10:43.400 that we otherwise would have queried the terminal for. 10:46.760 --> 10:50.520 API usage, if you look at everything in the end, 10:50.520 --> 10:52.120 I was actually surprised that it was so little. 10:52.760 --> 10:55.800 I would have expected to be more API calls for the underlying operating system 10:56.440 --> 11:00.360 that we would need than the end it was fairly straightforward. 11:03.400 --> 11:07.160 And then concluding, you look at the whole thing, 11:07.160 --> 11:10.040 I found in the end after Google Summer Code Project, 11:10.040 --> 11:13.640 the thing we achieved was actually way better than I expected, 11:13.640 --> 11:17.400 considering how snappy it was that we had a keyboard, 11:17.400 --> 11:22.440 a non-blocking handling, which has been a painful thing for a long time, 11:22.440 --> 11:23.640 at least in the lower language. 11:26.200 --> 11:28.840 But the main things here are the keyboard reading, 11:28.840 --> 11:31.800 and to make it independent over the platforms, 11:32.520 --> 11:35.400 and to display with that is really a painful thing to handle. 11:35.800 --> 11:37.880 Like for every single string you handle, 11:37.880 --> 11:40.840 you need to check with display columns, full summing. 11:43.080 --> 11:43.480 That's it. 11:45.240 --> 11:45.720 Questions? 11:46.360 --> 11:48.360 Thank you. 11:52.360 --> 11:53.640 Okay, the next speaker will come up. 11:55.640 --> 11:58.840 And also when people come in, try to focus a bit more. 12:00.840 --> 12:01.560 Any questions? 12:01.560 --> 12:01.800 Yeah. 12:06.440 --> 12:07.240 Sorry, can you say? 12:07.240 --> 12:10.040 It's a client, a product client. 12:11.800 --> 12:14.360 We could leave, basically it's an object, 12:14.520 --> 12:16.760 and it has methods for key handling. 12:16.760 --> 12:20.200 So you can inject FI modes if you want to. 12:21.320 --> 12:24.920 And I think, frankly, I don't use FI, so I don't know the shortcuts, 12:26.200 --> 12:28.840 but one of the entities that worked on it from the Google Summer Code, 12:28.840 --> 12:32.200 actually used, not sure it was FI, or maybe another, 12:32.200 --> 12:36.040 the competitor thing, but it should be possible. 12:37.640 --> 12:38.360 And you're going to see? 12:39.000 --> 12:40.120 Did you have any guidance? 12:40.120 --> 12:41.240 Just a small amount of images? 12:41.240 --> 12:43.080 And then you can see some of the colors. 12:43.080 --> 12:47.640 No, no, maybe next year Google Summer Code. 12:51.480 --> 12:55.400 I mean, it was mostly considering the platform in the penance. 12:56.040 --> 12:57.880 That was like the media thing to achieve. 12:57.880 --> 13:01.880 So we can have another build something that just works across the board. 13:01.880 --> 13:02.920 Something useful. 13:02.920 --> 13:03.800 That was the idea. 13:03.800 --> 13:08.040 And then the images, and etc, nice to have, but in a later stage. 13:09.320 --> 13:12.920 Yeah, that is the typical thing. 13:16.680 --> 13:17.240 Anything else? 13:21.000 --> 13:21.960 So, thank you very much.