Drag and Drop in C (Yes, it is possible! Let me show you how...)

  Рет қаралды 43,406

Tsoding Daily

Tsoding Daily

Күн бұрын

Пікірлер: 94
@Wesley-rn7oc
@Wesley-rn7oc Жыл бұрын
this channel is a damn gem
@mrmaniac9905
@mrmaniac9905 Жыл бұрын
factually and objectively accurate
@atlantic_love
@atlantic_love Жыл бұрын
How so?
@diegorocha2186
@diegorocha2186 Жыл бұрын
"Raylib font suits this project very well, let's load another font" Zozin 2023
@octagonal8905
@octagonal8905 Жыл бұрын
Yeaaaah ! Yet another EPIC zozin zezon !!!!!
@desertfish74
@desertfish74 Жыл бұрын
recreazional zozing is best zozing
@RenderDragon
@RenderDragon Жыл бұрын
Agree
@thebirdhasbeencharged
@thebirdhasbeencharged Жыл бұрын
Haven’t watched yet did he zoze all over the place?
@huistelefoon5375
@huistelefoon5375 Жыл бұрын
​@@thebirdhasbeenchargedyeah, right after he said "It's zozin time"
@ukrustacean
@ukrustacean Жыл бұрын
Kinda suspicious for him to use the letter 'z' -_-
@zennmyst5347
@zennmyst5347 Жыл бұрын
I hope you do a x11 drag n drop sometime too. Another great video! TY!
@kibels894
@kibels894 Жыл бұрын
Interesting, I've never seen a pointer to an array like that. Probably because it is such obscure syntax. Most code I've seen would have to use two variables or a struct to do that.
@Alexander-bk6oy
@Alexander-bk6oy Жыл бұрын
could you precise timestamp of where he use this obscure syntax plz ?
@7782
@7782 Жыл бұрын
19:00 You can't really take Julia as serious langauge because its Arrays starts at 1
@anon_y_mousse
@anon_y_mousse Жыл бұрын
Agreed there, arrays should always start at 0.
@jwadaow
@jwadaow Жыл бұрын
The 1st element should be 0?
@dots5641
@dots5641 Жыл бұрын
@@jwadaow address + index it makes sense in machines for first element to be zero because the first element of an array shares the same address as the array itself
@zeyonaut
@zeyonaut Жыл бұрын
@@jwadaow The 0th element should be indexed by zero. ;)
@edh615
@edh615 Жыл бұрын
@@dots5641 i want to program not play with memory addresses.
@CaptainBullzAQW
@CaptainBullzAQW Жыл бұрын
the thing about tsoding is that he always knew what he was doing on that code, awesome bro
@MichelHermier
@MichelHermier Жыл бұрын
Hi, about X macro, there is also another interesting change you can make, by making LIST_OF_MACRO(PLUG). In litterature it seems to be an evolution of X macros and can be used as a "map". That solution has the advantage that you are not enforcing the name of applied macro. As a consequence, the applied macro can be defined globally and reused if needed.
@pvt.ltd.yt_industries
@pvt.ltd.yt_industries Жыл бұрын
15:00 K&R describes a very similar scenario on page 122 under 5.12 "Complicated Declaration". Clearly: float* fs[2]; would be the normal way to describe an array of 2 pointers, but this is equivalent to float *fs[2]; because c doesn't care about whitespace here, and/or * associates from right to left rather than left to right (so *z->key returns *(z->key) [which will either segfault or give you a compile time error (not sure) because z is a pointer to a pointer so z->key is casting a struct pointer to the struct itself] instead of (*z)->key, a normal integer or whatever type key is). To my knowledge there are two things necessary to understand C types, the idea of this expression "mnemonic" in the words of Kernighan/Richie and the idea that for arrays you need to put the length inside the brackets. The mnemonic idea is that C, in reality, only has the "proper" written types char, int, long, float (and whatever structs you have) and you don't have anything "more" than this in the sense that int* is not really a "proper" type but only a hack, with the exception of void* which really is a type by itself. Sorry if my description is confusing but it will be clear with examples. In general we define these other types (taking fs as an example) using int ; For example, by int *fs; we mean to say "If we dereference fs we receive an int value" which is another way of describing int pointers, and pointers in general. Thus in K's and R's minds, the dereference operator is most important, and using it we define what pointers mean. This might be more natural to ASM programmers in the 70s since there's a "deref. operator" there, but instead of * you use [...] in intel or (...) in at&t syntax. I'm pretty sure there are different machine codes for these things (mov ax, bx has a different machine code than mov ax, [bx] I'm almost certain), so I mean to say it makes some sense to define it this way back then, though of course this is just my speculation and I don't have too much ASM experience, certainly not as much as people from the 70s. Now we move to function declarations. This only clicked in my head today, while reading K&R, but int f(int a, int b); actually follows this as well, what it says is "if we call f with (certain types) then it returns an int". This makes even more direct sense in C++ since we have operator overloading since, for a function called add, "if we call add with two int's it returns an int" and "if we call add with two float's it returns a float" are things you might actually say. In C you can't call a function with different types so you wouldn't say that, is what I mean. For function pointers, then: int (*f)(int a, int b) This translates to "if we dereference f and then call this value with two ints it returns an int". This makes sense in assembly again because functions of course are just labels, so really hard-coded addresses where there are blocks of machine code. And you do an "indirect call" i.e a call to a pointer in a register instead of a hardcoded address value. That would be, if the value of f is stored in register %rax, in intel syntax: ...push arguments onto stack or store in some other registers... call [rax] and I was surprised to find (just now) that in AT&T syntax it's (...push arguments onto stack or store in some other registers like before...) call *%rax Is this where C got the idea from? Not sure. I doubt both of them used it by coincidence but the idea of "pointing to" and an asterisk looking like a point make them a good match, so I don't know. Especially, of course, since AT&T owns Bell, and C famously is the successor of B standing for Bell, so I mean to say both K & R worked at Bell while they made it. So I'm going to say this is probably the case with 80% certainty. So then, we have described the "calling operator" in terms of pushing onto the stack (so, supplying arguments) then calling call. Then in intel syntax it's clear that we really are, literally, calling the dereferenced form of our variable f, and in AT&T syntax it's also quite straightforward, visually even more so (From stackoverflow I've heard that the reason AT&T doesn't use call (%rax) in analogue with intel is because you can actually do "call *(%rax)" to mean call the dereference of the dereference of %rax, in other words %rax would hold a function pointer pointer, a pointer to a function pointer. Not sure how you do this in intel, maybe call [[rax]] is legal). There is another thing, and this is much shorter, is how we can describe the number of elements in an array. This doesn't follow the formula since int a[5]; would mean "6th element (counting starting with 1) of a is int", which doesn't make sense. So instead, as a kind of hack, you can split this into two: int a[i] where i < 5; where i is of course >= 0, that's left assumed. Thus "ith element of a is int, for all i less than 5". It makes sense they had to do it in this hacky way since it's hard to think of a way to do it normally, and the method I described would be too lengthy to write down in syntax. This, to my knowledge, is all you need to know in order to parse various odd declarations. Let us then try to parse something from the cdecl front page: int (*(*foo)(void ))[3]; Looking at the right side as an expression acting on foo, and replacing [3] with [i], we turn this into "(*(*foo)(void ))[i] with i < 3 returns int" i.e. foo deref this value call this value with no arguments deref this value take i'th element of this value (treating it as an array) C can do this by itself since that's how they evaluate expressions. then the declaration is saying that this value received is int, that this expression returns an int. Thus, looking at the list and up, "foo is a pointer to a function with no arguments, and if we deref the return value we'll and take any element we'll get an int" i.e "foo is a pointer to a function with no args, with return value pointer to an int array of size 3" Let's see what cdecl says "declare foo as pointer to function (void) returning pointer to array 3 of int" - VRP, continued in replies
@pvt.ltd.yt_industries
@pvt.ltd.yt_industries Жыл бұрын
I had some trouble in this last step of converting from expr to this kind of "pointer" terminology. I think the disconnect comes from, possibly (again, speculation), that ASM programmers thought in terms of derefs first then pointers second and these days we think of pointers first and derefs second. The C declaration describes how exactly you would go about receiving a normal value, and since pointers by themselves are useless, that's actually what we're interested in. How would we describe something like this in another language? How do we describe function pointers, just the normal kind? Unfortunately I do not know. I think this is confusing because the underlying idea of the type is confusing, so I don't believe it's C's fault in some cases, and should be solved with some intermediate typedefs, though I don't have much experience so I don't know. Maybe another way of describing functions would be like this, similar to javascript (javascript has no pointers so I'm using & to mean pointer of): &( () => ( &(int[3]))); Or with better bracket usage: &[ () => &(int[3]) ] This seems easier to comprehend but also harder for the computer to typecheck, however I'm not sure. Is that why C did this in this way? To make type checking easier? I don't know how typechecking in compilers work. You could store a "type" as a list like before, like a stack with the first value on top: [deref, call with [the types of the arguments], deref, deref, INT] This could be stored as a linked list where call's have an extra pointer to another linked list containing types, so a linked list of type linked lists. I've treated arrays as simple pointers here, since C or C++ don't do out of bounds checking anyway, but you can include it only for printing purposes, so the programmer can see what it. [deref, call with [the types of the arguments], deref, array size 3 deref, INT] Then we can do typechecking like so, taking a few examples: f(1, 1) here we check the first value of the stack, and see that it's a dereference. Calling and dereferencing are "uncooperative" I'll call them. What I mean by this is that we can add numbers and some other things, and I will think about that in a second. But for now, since "deref" =/= "call", we'll call this a type error and print out "you cannot call a pointer" etc. Now this was a bad example because you actually can call function pointers in C++ at least, where C++ just deref's for you, but really you shouldn't be able to, they only added that to make things easier to use I think. We can implement this kind of behaviour though. If we get call while we have deref on top of the stack, we'll check right below deref and check to see if it's a call. Then we'll compare the lists of the calls to see if the types of the arguments are the same, in the usual way of comparing lists, like so: bool check_equal_type(stack 1, stack 2): //let top1 be top(stack1) and similarly top2 if top1 null and top2 null: return true if top1 null or top2 null: return false if top1 == top2: return check_equal_type(next_link(stack1), next_link(stack2); else //top1 != top2 return false; And if they match then we'll just do it. This description of comparing lists is the same way we do normal calls, we check if both types are calls then we check the argument list, and if they match then we say ok, type check has been passed and take call from off of the stack, that's the type of the new expression. If they don't match then we give a type error, saying we called the function with the wrong types. Now for things like adding, we check if there exists some kind of adding for this type. This is operator overloading in C++, but we have a hardcoded kind in C where we can add integers to pointers and instead of long addition we get the scaled version, scaled by the size of the pointed-to value of the pointer. So let's implement a sizeof function. If the top of the stack is deref then this is a pointer, so whatever size your registers are. For 64 bit computers, 8 bytes, for 32 bit computers, 4 bytes and similarly for 16 bit. If we have a call, then this is also a pointer so register size. sizeof for int's and float's and doubles etc. are hardcoded. I don't know how C does this or just casts it to a pointer, but we can also calculate the size of array simply by multiplying the size by the type of the next type. So here it is. size_t sizeof(type): switch(top of type): case deref: case call: return REG_SIZE case array: return size * sizeof(next_link(type)) case INT: return INT_SIZE case CHAR: return CHAR_SIZE . . . case LONG: return LONG_SIZE error "INVALID TYPE, compiler is broken" // or something idk exit(1); since we're the ones who made the type, we've made a mistake in making it so we exit after we tell someone to debug. If there was a type mismatch, or the user did some weird things, we should've figured out that back then, not now. Now of course for something like this: long a = *f; we just calculate the type on the LHS and check_equal_type with the RHS and if they aren't equal we say type error or maybe we try casting. [call with [the types of the arguments], deref, array size 3 deref, INT] vs [LONG] check equal type returns false so we try casting. We can in this case since call's can become pointers, but I don't know if C would let you. We'll think about casting in a bit. We are done with setting up one way of doing type checking. I don't know if C does it this way. We have gone slightly off topic. - VRP
@pvt.ltd.yt_industries
@pvt.ltd.yt_industries Жыл бұрын
I was too curious so I searched online about C types, but they don't talk about implementation so I assume it is compiler specific. But they mentioned structs. Structs are easy to implement I think, in the same way we implement the arguments of call: We take a linked list of types and check each one with each other. Finally for unions you can just let it go? I am not that sure about how unions work in C since I've never had a use for them. I know what they are but they are already fixed once you set them. If we have an int and a float union since we can more or less set in the type checker to allow a pass if its int or float, when it's checking between two things. So there should be an idea of a union and a collapsed union, I mean a union that is definitely using float and a union that is definitely using int, since you can only use one. Then comparing an indefinite union with a definite union will give a pass... I am checking the internet about unions... Hmm, wikipedia says that C implements it in a "type unsafe way" which I assume to mean they just give the user the bytes and they can look at it any way they like. This is very easy then, and I don't need to care about things like "definite unions". Casting is straightforward.
@revenevan11
@revenevan11 6 ай бұрын
Wake up babe, new files just dropped!
@ElPikacupacabra
@ElPikacupacabra Жыл бұрын
Parsing that C 'gibberish' is easy because you read it starting at the defined symbol, based __on the order of operator precedence__. So, in your case, fs is a pointer first, because it's enclosed in brackets. Without brackets, it's an array, because '[ ]' is a higher precedence operator. 😊
@wagsman9999
@wagsman9999 Жыл бұрын
Moving from Beast Mode to God Mode programming level.
@nocodenoblunder6672
@nocodenoblunder6672 Жыл бұрын
Very nice explanation on typing.
@bobby9568
@bobby9568 Жыл бұрын
This guy's level is insanely high 😅
@طاهرهپیلتن-ع7م
@طاهرهپیلتن-ع7م Жыл бұрын
14:47 Cool things to know! I learned it in a course titled Designing programming lanuages.
@philipphortnagl2486
@philipphortnagl2486 Жыл бұрын
like your recent approach of german sentences in between - meine Freunde
@quantumechanix7583
@quantumechanix7583 24 күн бұрын
God bless you, zozin!
@ChrisBNisbet
@ChrisBNisbet Жыл бұрын
Are you sure that the format of the data supplied to the callback follows the audio file format? I was fooling around with this and it appeared that the callback was always supplied 2 channels and at a fixed rate. The number of channels and rate seemed to match what Raylib prints out when audio is first initialised.
@TsodingDaily
@TsodingDaily Жыл бұрын
Oh shi! I would not be surprised if this was true. Raylib already did me like that. Thanks, I'll read the implementation!
@RenderDragon
@RenderDragon Жыл бұрын
Zozin, will you continue working on ded? Watched the last episode and it (ded) is pretty cool!
@TsodingDaily
@TsodingDaily Жыл бұрын
Yeah, I'll get back to it at some point. None of my projects are dead. They are just paused until the time is right. :)
@angelcaru
@angelcaru 8 ай бұрын
one of your projects is ded :)@@TsodingDaily
@surendra7856
@surendra7856 Жыл бұрын
Could someone please let me know how to learn and prepare for c language based Programming for 42 Wolfsburg course.?
@asepulven2768
@asepulven2768 Жыл бұрын
I would recommend watching Jacob Sorber videos. He teaches C in a good way. Always implement the stuff you learn.
@thacuber2a03
@thacuber2a03 Жыл бұрын
just wondering, what distro and DE/WM you use? for me it's interesting to look at
@kernelk1931
@kernelk1931 Жыл бұрын
he is using i3 Window Manager.
@nayan7574
@nayan7574 Жыл бұрын
i guess Debian with i3 and emacs
@michaelscofield4524
@michaelscofield4524 Жыл бұрын
And the distro is just some ancient Debian that he uses on a laptop
@kernelk1931
@kernelk1931 Жыл бұрын
also, he is using Debian 10.
@davidprock904
@davidprock904 Жыл бұрын
The most important end goal... use the least CPU Cycles possible!
@Celicaw8
@Celicaw8 Жыл бұрын
Why not increase the pointer size by the amount of channels * sizeof(float)? You could just create a wrapper around the pointer that the callback needs, put the pointer as the first member of the struct (so if raylib does anything with it, it'll still get the same address), and you can put anything else you want inside the struct. If raylib does multithreading shenanigans in the back, then just pad after the first member, so your stuff is on a new cache line and you won't have false sharing. @Tsoding Daily
@michaeldeakin9492
@michaeldeakin9492 Жыл бұрын
my understanding is that the whether shared libraries have their own heaps is OS and standard library dependent; I think I remember reading it's the case for Windows (though I don't know). For Linux, it's supposedly fine so long as they use the same standard library implementation.
@rogo7330
@rogo7330 Жыл бұрын
You basically reading declarations inside out, starting from the name of symbol. You want a pointer? *fs. What it points to? float [2]. You need those parentheses because square brackets have higher priority, or something like that. With functions it's the same shit. char (*fs(int))[2] - it's a pointer "fs" to function that takes one int argument and returns pointer to array of 2 chars, but since arrays degrading to pointers nobody writes functions that return arrays and instead you will see something like that. char *(*fs(int)).
@itsdrdy5551
@itsdrdy5551 Жыл бұрын
48:58: I noticed only now but how did the mono one pass this assert?
@HatsuSixtyOne
@HatsuSixtyOne Жыл бұрын
the assert only happens in the `plug_init` function. he doesn't use this function to load a file that was dropped.
@itsdrdy5551
@itsdrdy5551 Жыл бұрын
@@HatsuSixtyOne ah okay thanks, my bad
@hwstar9416
@hwstar9416 Жыл бұрын
afaik global variables in dll are stored in the memory of process that links with the dll, not the dll itself. Maybe that has something to do with error = false
@Purkinje90
@Purkinje90 Жыл бұрын
Babe wake up, new file just dropped
@bbq1423
@bbq1423 Жыл бұрын
Drag and drop to import images is cool and all, but how about drag and drop to export 😎
@salman0ansari
@salman0ansari Жыл бұрын
how tf he's so good
@bmno.4565
@bmno.4565 Жыл бұрын
Very creative use
@vasilecaspirovschi3925
@vasilecaspirovschi3925 Жыл бұрын
Who knows what theme for the emacs does he use? Thanks in advance
@anon_y_mousse
@anon_y_mousse Жыл бұрын
Maybe I'm just weird, but when I name the constituent parts of a dynamic array structure I always have { len, mem, data }. You're right about declarations in C being shit, which is why I typedef just about everything and glom them together with structs, especially when I'm dealing with function pointers. If POSIX really does reserve *_t typenames then I'll have to add that to my list of reasons to hate it, along with the garbage that is all the exec*() functions. Also, I don't see this music visualizer in your list of repositories. Is it private and/or are you not going to post it?
@quadric_
@quadric_ Жыл бұрын
What's the issue with the exec* functions? (I genuinely think they are quite usable)
@flobuilds
@flobuilds Жыл бұрын
Soooo basically you could use this Website as a parser that can compile english to c and then Compile this with eg gcc? So we can code in english now?
@alejandroulisessanchezgame6924
@alejandroulisessanchezgame6924 Жыл бұрын
How can you do some drag and drop system like unreal blueprint?
@engineerabi
@engineerabi Жыл бұрын
Great sir
@skope2055
@skope2055 Жыл бұрын
nice
@vidstige
@vidstige Жыл бұрын
What font are you using?
@yaksher
@yaksher Жыл бұрын
I think what may have happened is that the DLL can have uninitialized global data, but not initialized global data?
@johanngambolputty5351
@johanngambolputty5351 Жыл бұрын
I haven't done much c (actually mostly python), but I guess the way to maybe make sense of something like float (*fs)[2] is that both the pointer and array-like nature of the variable are communicated through the name of the variable as supposed to the type. When you have float *arr, you say that once you dereference arr, you end up with float, when you say float arr[2], you say that once indexed you get a float. So, since (for some infuriating reason) c types are right to left (and often inside out), float *fs[2] is, once you index you get something that can be dereferenced to a float, but you wanted to dereference first and get something that can be indexed for floats, so you put the reference inside brackets to make it happen first. It would be cool if there were an lsp that added cdecl annotations in editor.
@MetinCloup
@MetinCloup Жыл бұрын
Drag and drop in i3wm sounds like a lie 😅 Ps : İ haven't watch video yet
@DassVeryGood
@DassVeryGood Жыл бұрын
When I saw that pointer to an array I was like, “Oh that kinda looks similar to a function pointer… wait… holy shit that’s so cool.” And then I saw this void (*fun_ptr_arr[])(int, int) = {foo, bar}; Which is an array of function pointers. This is wild. I constantly learn or witness weird stuff about C and C++.
@mrmaniac9905
@mrmaniac9905 Жыл бұрын
Can we get a series on building a web render engine using nanovg??? would be super cool
@baumstamp5989
@baumstamp5989 Жыл бұрын
so as a rather programming newb. why would drag & drop not be possible in c, when almost any cpp code can be in some way transcompiled to c code or to expand the question. what linux/windows application could not be coded in assembly / c , when these languages are even lower level than most modern languages that work with higher abstraction levels apart from the fact of having to write a lot of complicated code ofc...
@adamm450
@adamm450 Жыл бұрын
it has nothing to do with programming language. its more like there is a expectation that there would be less tools and libraries that do this for C or that the process would be cumbersome because of how low level C is. these expectations are based on an assumption that usually when doing GUI people would pick a higher level language hence there is an assumption there would be more libraries for these higher level languages or this feature is directly built into a standard library of such language. neither of which is necessarily true.
@michaelscofield4524
@michaelscofield4524 Жыл бұрын
It's not that it literally can't be done. But most would think that the complexity is just too much to bother adding to an application. So the fact that he implemented a mostly working functionality by the end is very impressive. About the comments on C and Assembly. Well yeah, if you hace access to the system's LibC (the common way to interact with the OS from a program), you can pretty much do anything with C and/or assembly.
@Bobbias
@Bobbias Жыл бұрын
Assume you're using a turing complete language, there are very few true limitations on what a language can do. It's more about what's feasible/sensible to do. Also, the title is clickbait.
@baumstamp5989
@baumstamp5989 Жыл бұрын
thank u guys!
@sabjekill
@sabjekill Жыл бұрын
@@michaelscofield4524 Not really impressive since he just used the API supplied by the framework he uses, the heavy lifting is isn't done by his code.
@Qubyts
@Qubyts Жыл бұрын
Este man se la sabeeee
@AlguienMas555
@AlguienMas555 Жыл бұрын
F%cking awesome!! 8>{|}
@TheSkronk
@TheSkronk Жыл бұрын
Maybe show what song is playing in the window title?
@GuidoHatsizz
@GuidoHatsizz Жыл бұрын
Your amazing
@lhomme_flaneur
@lhomme_flaneur Жыл бұрын
at first I though it's a website and you made drag&drop for divs lol
@nameless5724
@nameless5724 Жыл бұрын
WWWWOOOOOOOWWWWW
@alejandroulisessanchezgame6924
@alejandroulisessanchezgame6924 Жыл бұрын
In c
@wijiler5834
@wijiler5834 Жыл бұрын
W zozin zezon
@ArmandXie
@ArmandXie Жыл бұрын
Could we port this to wasm?
@evalinor
@evalinor Жыл бұрын
with C possible almost all=)
@valbogda5512
@valbogda5512 8 ай бұрын
In Soviet Russia. You do not pragramme C, C programmes you.
@gepron1x
@gepron1x Жыл бұрын
Набери веса бро
@alvaronaranjo2589
@alvaronaranjo2589 10 ай бұрын
That made 0 sense to me
@BitHeavenOfficial
@BitHeavenOfficial Жыл бұрын
Hello, are you have any discord or telegram channel for contact?
I tried React and it Ruined My Life
1:19:10
Tsoding Daily
Рет қаралды 142 М.
The Best Coding Interview Question Ever
1:21:27
Tsoding Daily
Рет қаралды 38 М.
Osman Kalyoncu Sonu Üzücü Saddest Videos Dream Engine 269 #shorts
00:26
CAN YOU DO THIS ?
00:23
STORROR
Рет қаралды 39 МЛН
This mother's baby is too unreliable.
00:13
FUNNY XIAOTING 666
Рет қаралды 43 МЛН
My Program Sucks!
59:09
Tsoding Daily
Рет қаралды 23 М.
Is C++ better than C?
1:46:10
Tsoding Daily
Рет қаралды 48 М.
Emulating a CPU in C++ (6502)
52:28
Dave Poo
Рет қаралды 996 М.
Let's Create a Compiler (Pt.1)
1:11:03
Pixeled
Рет қаралды 541 М.
Windows Development on Linux
2:15:17
Tsoding Daily
Рет қаралды 51 М.
I regret doing this...
1:20:07
Tsoding Daily
Рет қаралды 80 М.
Stop Recommending Clean Code
27:05
ThePrimeTime
Рет қаралды 518 М.
Capturing Sounds with C
1:51:09
Tsoding Daily
Рет қаралды 23 М.
Osman Kalyoncu Sonu Üzücü Saddest Videos Dream Engine 269 #shorts
00:26