Тёмный

Unit of Work Pattern in Unity 

Infallible Code
Подписаться 72 тыс.
Просмотров 50 тыс.
50% 1

Опубликовано:

 

14 окт 2024

Поделиться:

Ссылка:

Скачать:

Готовим ссылку...

Добавить в:

Мой плейлист
Посмотреть позже
Комментарии : 111   
@InfallibleCode
@InfallibleCode 2 года назад
Sign up for the Level 2 Game Dev Newsletter: eepurl.com/gGb8eP Download the project at www.patreon.com/posts/project-unit-of-60029495
@harrysadlermusic
@harrysadlermusic 2 года назад
Very nice presentation. It's nice to see you exploring areas that other game dev channels don't cover.
@houbeenie
@houbeenie 2 года назад
With everything you've learned in the past 2 years, is there any chance you could redo the video you posted on the 10th of december on 2019. The Unity Code Review, using the same code. But apply everything you learned in the past 2 years (if anything changed). Would be cool to see how your refactoring evolved over that period.
@InfallibleCode
@InfallibleCode 2 года назад
Cool idea!
@thebebinator
@thebebinator 2 года назад
Hey Charles, love the new format and the new content you've been pushing out. I also think there's a bit of room for improvement compared to your older videos. I think the videos might be a bit too short since they rush through some key things. For example, you say "All our problems are solved" but you don't explain how or why they are now solved. I think it's because the unit of work makes sure all the repositories are updated in tandem every time they are changed, but its not super clear in the video, why delegating the save fixes everything.
@thebebinator
@thebebinator 2 года назад
Just elaborate a bit more, how does the unit of work solve the opening-streams problem? How does the unit of work fix slow data access? it seems like it's just adding a step that forwards a call to the original code. I think the real solution is from the "little cleanup" that you mentioned, (calling one save vs two), but this happens so fast it's super hard to follow. I think stressing this step might be the most important part of the 2nd half of the video, but it's kind of glossed-over.
@CyberAngel67
@CyberAngel67 2 года назад
@@thebebinator I have to agree, this was the first time that I have actually physically hit that Dislike button on any of these videos by InfallibleCode. I know these patterns well, as I have used them for 20 odd years in Web Development, but even I was left with WTF did you just cut the most important part of this video out of the video!
@MarekNijaki
@MarekNijaki 2 года назад
@@thebebinatorI think it's not only that what you mentioned, Charles use 'await' in 'UnitOfWork' save method. This also changes how save works. Will read tomorrow what it does and try to explain it here
@MarekNijaki
@MarekNijaki 2 года назад
So 'await' have nothing to do with 'UnitOfWork' principle itself. 'await' is just a keyword used when you handle asynchronous operations. Seems main reason of 'UnitOfWork' was incorporating saving data into one place. Similar to one big transaction instead of multiple smaller ones. As Charles said: 'UnitOfWork' keeps track of every changed that happened and write them all in same time when you tell it to. So +1 to explenation by 'thebebinator' and +1 to request of explaining it in more details in video.
@Sindrijo
@Sindrijo 2 года назад
Yes that is because the original problem was that an exception might occur in between the two calls to save, causing only the first one to be called and therefore the data would be corrupted, the player would have the item and loose the gold but the shop would not have the item removed nor gold added. Often you would code this as an 'validated transaction' meaning that all operations of the transaction must be completed successfully, otherwise all changes must be discarded or partial changes rolled back. For example you could stor the state of the repositories before each change, the try to make the change and if an exception occurs catch it, restore the state to before any partial changes of the transactions were made. The unit of work kind of encapsulates this because the player and items are part of the same data context, but it is not explained clearly enough in the video, and I think the video glossed over this fact a little too readily. A different way to think of it is the totality of the changes of the transactions written to a buffer which is flushed all at once at the end in the save call, due to it being called last everything before it must have been successful. The data context is a representation of the data into memory, modfying it in the transaction and the writing the full state of the context back to the source, if anything fails the transaction the datacontext must be discarded and reloaded before trying again since it contains and invalid state, this also means that sharing data contexts can be dangerous if you are doing transactional operations.
@matt9897
@matt9897 2 года назад
Didn’t know your channel before, but credit for the relaunch. This high level concept stuff, understanding how lower level coding concepts fit into overall projects, is valuable. Thanks.
@AvalancheOnline
@AvalancheOnline 2 года назад
Im not sure if this is way to go, its web dev pattern and orm-s wich use it are known from being slow. If you build for example server side of game then you can get way better result with one instance that will do direct queries in async way by prepare statements / stored procedures without repo / uow.
@fmproductions913
@fmproductions913 2 года назад
I think it still makes sense to look at something like atomic transactions, where either all steps are computed and stored, or none (in case of an issue). But I agree, it heavily depends on the use case. Not sure how this is usually modelled though (as I have usually just done individual put/post/delete requests) with a client/server architecture. Maybe there is a post request to a certain url with the relevant information in the payload (player id, store id, id to identify action = purchase, amount of money transacted and item[s] that are bought)? On the server side of things, what you mentioned is probably faster yeah.
@eddyeffy
@eddyeffy 2 года назад
Entity framework screaming all over the place, this is HIGH QUALITY CONTENT
@KeyboardKrieger
@KeyboardKrieger 2 года назад
This channel is exactly what I needed. Most GameDev channels are good for design or marketing but few have good coding topics. I can literally see how my code gets worth by making games and especially GameJams. It's a whole other thing working as a normal developer outside the gaming industry. Often when I see GameDev code I want to punch the creator...and also often I recognize my code is the same crap and being a solo dev I have no one to blame for. I'll definitely watch your videos regularly to see common patterns used in GameDev. PS: Sorry for ranting, but today is day 3 of refactoring my own code xD
@justadude93453
@justadude93453 2 года назад
I tend to create a base class that inherits from scriptable objects for persistence (during gameplay) that knows how to interact with a separate JSON serialize/deserialize system and then a system that can either save locally or push those JSON files to a backend server.
@BrianDriscoll
@BrianDriscoll 2 года назад
I'm using Phaser for HTML 5 games, but this is still very helpful. As a beginner who's past the point of just syntax and applying the concepts from zero to real deployed game, learning about structuring data and systems is very insightful. Thanks!
@FuzzyDPozzy
@FuzzyDPozzy 2 года назад
DataContext reminds of wpf :P cool tutorial as always :D
@momonyaro
@momonyaro 2 года назад
Great video! There seems to be some heavy compression at points in the video though, noticed it mostly at 8:26. It hurts a bit when the visual presentation is so well done haha.
@jmguillemette
@jmguillemette 2 года назад
Interesting pattern. I wish at 10:03 instead of just calling save on the data context you showed first changing the in memory values. when the video jumps straight to the conclusion, i was left puzzled. We put an abstraction layer between the service layer and the context.. but the abstract was just delegating with no additional steps. I had to rewatch parts of the video to realize you mean to put more logic in this layer but didn't for speed in the video.
@szaboattila155
@szaboattila155 2 года назад
You mean like the Unity of Work class should call save during a loading screen ? I am still puzzled to be honest.
@StevenBloomfield
@StevenBloomfield 2 года назад
Agreed. I don't see what his example actually accomplishes. I feel like something got edited out.
@Muskar2
@Muskar2 Год назад
The difference is that he had a Repository implementation for each individual type (shops and players) that he then saved progress to, and the UnitOfWork captures an entire transaction of all the changes made to all different repositories, which then is attempted to be saved at the same time in the end. Of course, you could implement some rollback functionality if it failed and so on, but it still has a different function. I don't like it very much though, because the implementation depends on knowing that the repositories are both using the same DataContext object as we're referencing in the field. And I agree that it felt very rushed in the video.
@christianstamati
@christianstamati 2 года назад
Hello Charles, how about retreiving the sprites and other resources of the shop items? Let's say I want to download song item. They will have a cover and the actual audio, the repositories will download the items, how about the cover and the music clip to play? Should I store it in the item as a url string and after I will have another web request to download it? Thanks! Amazing content btw, I just subscribed to Patreon! Can't wait to see more!!
@badverb9267
@badverb9267 2 года назад
Very class, polished video.
@InfallibleCode
@InfallibleCode 2 года назад
Thanks!
@null21235
@null21235 2 года назад
Thanks for this quality video 🥳
@marko9900
@marko9900 2 года назад
Unit of work sounds alot like Transaction in enteprise software. All the changes made within transaction will be persisted in the end, and if any error occurs then in general all the changes are dropped and database remains unchanged.
@justinwhite2725
@justinwhite2725 2 года назад
Huh. I've been using he repository patterm without even knowing it. Was something I came up with myself. I call it an 'interpreter' that interpretes the raw data from the source. Most of the time this is essentially read only (server data) but I also use it for player preferences and other save files where he interpreter is also responsible for saving the data if it changes. 3:42 nice yeah. Mine are abstract too. In an early version they were interfaces, but I was using scriptable objects. You can serialize interfaces with Odin Inspector but this breaks unity cloud build for some reason (something about the AOT compilation process). I wound up using Abstract serializable classes to base the scriptable objects from.
@olon1993
@olon1993 2 года назад
This video is top notch! I NEED MORE OF THIS IN MY LIFE! Love it!
@box02
@box02 2 года назад
This is great. Is this your first design pattern in unity video ?
@InfallibleCode
@InfallibleCode 2 года назад
I have a few others on my channel in the "Practical Game Development" playlist
@kevonboxill9455
@kevonboxill9455 2 года назад
Loved the content but I still fell pretty lost as you never showed where the other persistance objects where referenced, also how did you populate the shops Repository with the shop and ids. So many other questions were raised by the end of the video that it feels a bit incomplete
@LesusGames
@LesusGames 2 года назад
Super helpful. Thanks.
@FullMe7alJacke7
@FullMe7alJacke7 2 года назад
Great content!
@pudgystump
@pudgystump 2 года назад
Love it.
@fmproductions913
@fmproductions913 2 года назад
Amazing video. I think Unit of work is a pattern I should explore outside of the Unity too. When working with data bases and object relational mappers, it seems like this is always properly implemented, but I never thought about designing such a system myself. Learned a lot in this video.
@InfallibleCode
@InfallibleCode 2 года назад
Glad to hear it!
@pliniomourao
@pliniomourao 2 года назад
you should change the description from: "Download the project at... " to: "Become a Patreon to be able to download the project at... " It is misleading information! I thought it would be a github link, but your post is locked. Nice video though. Cheers.
@HumbertoBytes
@HumbertoBytes 2 года назад
Another well put together video :)
@TheKr0ckeR
@TheKr0ckeR 2 года назад
Great guide! What's the theme you are using?
@tmkaplan94
@tmkaplan94 2 года назад
Hello! I know this is random, but what boilerplate text do you use for new Unity scripts? I seem to remember you talking about it in one of your videos, but I can’t remember which one
@Kugelschrei
@Kugelschrei 2 года назад
Could you make a video about metworking solutions? There is MLAPI, Photon Pun2, the upcoming Photon Fusion, and probably some peer to peer stuff through steam
@davidmcdonnel4831
@davidmcdonnel4831 2 года назад
Event loop would be a good extension topic.
@ingvarvd5885
@ingvarvd5885 Год назад
Hmm.. I do not understand how does using Save method from Unit of Work save performance? We still write to json each time we call Save..
@sabatheus
@sabatheus 2 года назад
When using JSON files to save data in, does Unity hide or obfuscate it after the build? I'm worried that people can open and modify the data values (give themselves 1M gold, for example). Is it safer to use Scriptable Objects?
@MrTomateSalat
@MrTomateSalat 2 года назад
No - it is just json. ScriptableObjects are not an alternative because they're not meant for persistence (changes there will be lost once the game is closed). But you could use a different way for serialization like the class BinaryFormatter. Then the file is less readable (but ofc still modifiable).
@fmproductions913
@fmproductions913 2 года назад
Like MrTomateSalate said, it's just a regular json file - if you want to protect it from user changes, you can add an encryption step when saving and an encryption step when loading. With that said, anything that is only on the client side is technically unsafe, players might be able to reverse engineer the relevant logic for encryption and decryption. But it should keep most users away from accomplishing that.
@fmproductions913
@fmproductions913 2 года назад
@@matthiasmax2849 Yep, there's not much of a point to prevent save state adjustments in purely single player/offline games. If a player really wants to do it, probably better to make the process not unnecessarily complex.
@rohit271290
@rohit271290 2 года назад
Awesome.Thanks!!
@larryd9577
@larryd9577 2 года назад
Behold, that the JSON serialization is humanreadable. If you want to use it in a released game on the players harddrives, use a symmetric encryption (e.g. AES) before writing the bytes.
@dealloc
@dealloc 2 года назад
This was an example. You don't need encryption if this was for a offline-only game. But the for an online game, the data source would obviously not be a JSON file stored on the client's computer. It would be a connection to an API that handles authorization itself. Also it doesn't matter whether something is human readable, if the data doesn't store private information (i.e. passwords, keys, etc.). Rather, it should ensure integrity, such that the data cannot be changed without the private key.
@larryd9577
@larryd9577 2 года назад
@@dealloc if you have your game state in a JSON stored on the client, it is even easier to hack your game than with a cheat engine. Just a thing i wanted to point out.
@dealloc
@dealloc 2 года назад
@@larryd9577 And my point is that it doesn't matter unless the game is online (i.e. multiplayer, high scores, etc.).
@MidnightSt
@MidnightSt 2 года назад
...any particular reason except "it is the default when making new C# file from unity", why your DataContext and Repository classes inherit from MonoBehaviour? Because to me, they seem like something that should inherit from nothing, and live as a pure in-memory object in a property of your global GameManager object, or something like that.
@diligencehumility6971
@diligencehumility6971 2 года назад
I don't see how the UnitOfWork class changes anything? It's just a wrapper for the DataContext? It doesn't ensure saves are rolled back if an error happens. It doesn't do anything?
@Sproeikoei
@Sproeikoei 2 года назад
It ensures separation of data and functionality. Its intent is to be a pattern that solves this particular problem. Not by itself to ensure datasafety. But if you want datasafety you can now easily put your implementation of how you want to handle these cases. If you want to only handle it on the business logic's side you don't have to make any changes to your data side and vice versa.
@diligencehumility6971
@diligencehumility6971 2 года назад
@@Sproeikoei Doesn't make sense to me. There is literally no difference between between DataContext and UnitOfWork. DataContext has two lists (shops and coins) and so does UnityOfWork. UnitOfWork just calls "Save" on DataContext. So it's a wrapper. It doesn't do anything. I don't see how it separates any concerns or solves any problems
@NadjibBait
@NadjibBait 2 года назад
@@diligencehumility6971 Yes, his implementation has no meaning at all, since he's calling Save method of the individual repositories before calling again Save in the UoW... a real UoW implementation keeps track of all changes in memory, then commits them (save) when a Save method is called, or in most cases, when the UoW is disposed like this: using (var unitOfWork = new UnitOfWork()) { // data changes here are tracked automatically, no need to call .Save on anything var player = unitOfWork.Players.GetById(123); var shop = unitOfWork.Shops.GetById(321); player.Money -= 100: shop.Money += 100; player.Items.Add("Sword"); shop.Items.Remove("Sword"); } // here data is saved without calling any Save method, since UnitOfWork is IDisposable, saving occurs in Dispose() method which is called automatically since we're using a "using" block.
@vr77323
@vr77323 2 года назад
@@NadjibBait I'm actually kind of confused as to how he even saves the data. His UoW has 3 properties -> dataContext, Players(which has its own dataContext) and Shops(Which also has its own dataContext). So how does calling dataContext.Save() actually make the Players and Shops save?
@NadjibBait
@NadjibBait 2 года назад
​@@vr77323 No idea...
@flyingnat
@flyingnat 2 года назад
What's the name of the BGM you used in this video?
@streamlineddesigns711
@streamlineddesigns711 2 года назад
What are the difference's between the repository pattern and the registry pattern?
@sebastianking7713
@sebastianking7713 2 года назад
Nice video! So do you need the Save method or even the dataContext on the repository anymore? Once you have the UnitOfWork, it seems to become redundant right?
@InfallibleCode
@InfallibleCode 2 года назад
The advantage of the individual repository implementations is that you can implement special functions depending on the data structure you’re trying to access. For example, the Shops repository could have a unique method called “FindAllByInventoryCount(int count)” or something to that affect. (Not a great example haha)
@MarekNijaki
@MarekNijaki 2 года назад
Question: why 'DataContext' method is called 'Set()' and not 'Get()' as it clearly get data, not set it?
@NadjibBait
@NadjibBait 2 года назад
it just means a "Set" of data (noun, which means a collection of data), not "to Set" verb.
@MarekNijaki
@MarekNijaki 2 года назад
@@NadjibBait All mothod names should be a verb, thats why I asked. So I still belive name was incorrect. If the intent was as you said I would propabbly name it 'GetDataset()'
@NadjibBait
@NadjibBait 2 года назад
@@MarekNijaki Is it a method? I saw it was a property, Set.
@NadjibBait
@NadjibBait 2 года назад
Yes checked again, you're right, it's a method, should have been a property thought, but it's ok, since we know it's a repository then we know that Set() refers to a collection. But if it was me, I would have made it a property to avoid any confusion.
@emad-dev
@emad-dev 2 года назад
I have been using this pattern for a long time, but i don't know this is a pattren called Unit of Work.
@waymanharris1284
@waymanharris1284 2 года назад
#Awesome
@potatopassingby
@potatopassingby 2 года назад
this is not important at all, just my brain not letting me move on but in the Load() of the JsonDataContext there is a FilePath that I can't seem to figure out where it comes from. how is it there? where are the red squiggly lines? D:
@melikechoc0
@melikechoc0 2 года назад
I believe Entity Framework also follows unit of work pattern
@lewaplay
@lewaplay 2 года назад
Json ser\deserialization is slow, keeping it in byte array is faster :)
@dealloc
@dealloc 2 года назад
Parsing and writing JSON is pretty fast with the right implementation. It's definitely negligible with small data. It's more correct to say that byte arrays is more _compact_ than JSON, but it doesn't automatically make it _faster_. It depends on the implementation and the layout of your data. Another complexity that byte arrays add is that if you want to be able to change the layout, add additional data and more, you will need a way to version it in order to support backwards compatibility-which can add additional overhead to your parsing. This is relatively easy to do with JSON, as it is simply plain text and not binary. If you work with really large JSON data, there are also ways to stream JSON, e.g. by separating data by a delimiter (e.g. newline) that can parse objects individually.
@SubjektDelta
@SubjektDelta 2 года назад
Finally!
@cew182
@cew182 2 года назад
So it looks like the "little bit of cleanup" at the end was the important part of this. He only calls save after all of the changes have been made. This could have been done without the unit of work. The changes are being made to the contexts in both examples. Then an async call is made to save at the end. I just don't see any point to a unit of work that does nothing but straight passthrough. I also don't see why this is a component at all. After a thorough and well-paced video explaining the problem in detail the solution was rushed to the point that the changes could hardly be seen and there was no explanation of how the solution works or solves the problems. This was so disappointing.
@halivudestevez2
@halivudestevez2 2 года назад
that "IF" with typecheck smells - but okay for the sake of the example.
@jasonzil
@jasonzil 2 года назад
Love the videos, but I wish some of the shots of the code would be on the screen for longer and the edit were a little less frantic. And would love a video on data binding models like MVC and MVVM
@fauzannrifai
@fauzannrifai 2 года назад
this data pattern is a big thing, most people out there just talk about mechanic
@focusedcoding7439
@focusedcoding7439 2 года назад
Is it new pattern? It's the first time I've heard of it
@__________________________4597
@__________________________4597 2 года назад
no it is used almost everywhere in webapp development, not great for gamedev since it tends to be slow. but does the job for the example.
@bakenbard
@bakenbard 2 года назад
please highlight lines of code you talking about, because showing full file and just talk isn't really a good idea
@alekjwrgnwekfgn
@alekjwrgnwekfgn 2 года назад
Great content. I’m an artist who has been trying to learn to code on the side. I can’t tell you how many courses I’ve sat through where they just get you to copy what they are typing, and I’ve learned nothing.
@InfallibleCode
@InfallibleCode 2 года назад
Glad it helped! We’re actually working on our own course, join the mailing list if you’d like to be notified when it drops early next year.
@alekjwrgnwekfgn
@alekjwrgnwekfgn 2 года назад
@@InfallibleCode yep done. The course looks good.
@kevonboxill9455
@kevonboxill9455 2 года назад
@@InfallibleCode hey all the best for the new year and stuff, but it seems as tho u are'nt addressing the negative comments in this chat as they are very valid..the number of ppl not understanding the content is far greater than the number of ppl giving you credit..I love your content but this is pretty sad to see that you haven't even acknowledged the constructive criticisms thus far..will continue reading the comments to see if you addressed it.. reply discouraging tho..
@lucasmontec
@lucasmontec 2 года назад
Those guard clause ifs on the buy method should be avoided. It is better to get an exception than to get no error and no behaviour.
@AdaptorLive
@AdaptorLive 2 года назад
You basically skipped over the actual important part and subject of the video. :(
@AddyVDH
@AddyVDH 2 года назад
Calling classes data structures honestly feels blasphemous. PSA that you should probably learn CS theory separately from Unity. This way you won't be mislead by confusing/incorrect jargon.
@doismilho
@doismilho 2 года назад
I'm completely lost tbh. Did not understand what the issues with concurrency were or how the code actually works and/or solves them.
@notsocasualgamedev
@notsocasualgamedev 2 года назад
The way I understood it is that he's saving the data async. So inside his code he's performing two async writes, which might lead to data loss. Because the second save might happen while the first one hasn't finished yet. Or if you're on the web you're not guaranteed that two requests to the server from the same device will reach their destination in the same order you sent them, and the same goes for the server response back. So he basically created this unit of work abstraction, where he gathered the pending changes, so he can handle them in one go.
@doismilho
@doismilho 2 года назад
@@notsocasualgamedev thanks a lot, its muuuch clearer now. Wish the dude making the video would actually explain stuff.
@fitstrong167
@fitstrong167 2 года назад
I really like your content , this video great Topic but you made it short and unclear ....sorry but littl bit more would made great Repo Design video ....bitter taste :/
@HectorVizyon
@HectorVizyon 2 года назад
I'm not sure maybe just me but i think you need to stay a bit away from the camera because its really weird like this.
@tarutso
@tarutso 2 года назад
Agreed, slightly too close
@kayumiy
@kayumiy 2 года назад
It is so difficult to understand.
@fearmear
@fearmear 2 года назад
Instead of saving instantly hold the data in memory for X time and then save once there were no new changes during X time. Idiot-proof approach.
@thedude8134
@thedude8134 2 года назад
Whoah dude, did you actually delete my comments?)
@InfallibleCode
@InfallibleCode 2 года назад
No, I don't delete comments. What did it say?
@thedude8134
@thedude8134 2 года назад
@@InfallibleCode I've written two comment regarding the video and it disappeared. Weird. Sorry.
@thedude8134
@thedude8134 2 года назад
I don't get it, I tried to write it again it disappeared again lol. Whelp. Whatever. It was about naming conventions, a suggestion. Something's up with RU-vid.
@toastyshrimp1882
@toastyshrimp1882 2 года назад
@@thedude8134 RU-vid automatically deletes comments that have censored words in them. You might be tripping it on accident, it's happened to me quite a few times despite being rather inane
@thedude8134
@thedude8134 2 года назад
@@toastyshrimp1882 I dont think i've written anything offensive or inappropriate there ... But you might be right, thanks. I've also just read that they could review comments before actually posting them, especially for a few specific countries. Censorship is real nowadays...
Далее
Unity 2021.2 - New Features for Programmers
7:50
Просмотров 39 тыс.
4 Tips for Refactoring Your Code for Readability
14:58
这到底是怎么做到的 #路飞#海贼王
00:10
Просмотров 4,4 млн
Which part do you like?😂😂😂New Meme Remix
00:28
Here's How You Should Be Thinking About Data
10:56
Просмотров 35 тыс.
Microservices are Technical Debt
31:59
Просмотров 509 тыс.
The 6 Design Patterns game devs need?
24:20
Просмотров 370 тыс.
What does larger scale software development look like?
24:15
How Thinking in Systems Can Improve Your Code
10:16
Просмотров 32 тыс.
New C# Features in Unity
12:39
Просмотров 45 тыс.
Repository Pattern
11:08
Просмотров 68 тыс.
这到底是怎么做到的 #路飞#海贼王
00:10
Просмотров 4,4 млн