рдпреВрдирд┐рдЯреА 3 рдбреА рдИрд╕реАрдПрд╕ рдФрд░ рдЬреЙрдм рд╕рд┐рд╕реНрдЯрдо

Unity3D рдХреЗ рд╕рд╛рде, рд╕рдВрд╕реНрдХрд░рдг 2018 рдХреА рд░рд┐рд▓реАрдЬ рдХреЗ рд╕рд╛рде, рдпрд╣ рд╕рдВрднрд╡ рд╣реЛ рдЧрдпрд╛ рдХрд┐ рдореВрд▓ (Unity рдХреЗ рд▓рд┐рдП) ECS рдкреНрд░рдгрд╛рд▓реА рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рдП, рдЬреЛ рдЬреЙрдм рд╕рд┐рд╕реНрдЯрдо рдХреЗ рд░реВрдк рдореЗрдВ рдорд▓реНрдЯреА-рдереНрд░реЗрдбрд┐рдВрдЧ рдХреЗ рд╕рд╛рде рд╕реБрдЧрдВрдзрд┐рдд рд╣реЛред рдЗрдВрдЯрд░рдиреЗрдЯ рдкрд░ рд╕рд╛рдордЧреНрд░реА рдмрд╣реБрдд рдЬреНрдпрд╛рджрд╛ рдирд╣реАрдВ (рдЦреБрдж рд╕реЗ рдПрдХрддрд╛ рдЯреЗрдХреНрдиреЛрд▓реЙрдЬреАрдЬ рдФрд░ рдпреВрдЯреНрдпреВрдм рдкрд░ рдЕрдиреБрджреЗрд╢рд╛рддреНрдордХ рд╡реАрдбрд┐рдпреЛ рдХреА рдПрдХ рдЬреЛрдбрд╝реА рдкрд░рд┐рдпреЛрдЬрдирд╛рдУрдВ рдХреЗ рдПрдХ рдЬреЛрдбрд╝реЗ)ред рдореИрдВрдиреЗ рдИрд╕реАрдПрд╕ рдХреЗ рдкреИрдорд╛рдиреЗ рдФрд░ рд╕реБрд╡рд┐рдзрд╛ рдХреЛ рдорд╣рд╕реВрд╕ рдХрд░рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХреА, рдЬрд┐рд╕рд╕реЗ рдПрдХ рдЫреЛрдЯрд╛ рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдХреНрдпреВрдмреНрд╕ рдФрд░ рдмрдЯрди рд╕реЗ рдмрд╛рд╣рд░ рдирд╣реАрдВ рд╣реБрдЖред рдЗрд╕рд╕реЗ рдкрд╣рд▓реЗ, рдореБрдЭреЗ ECS рдХреЛ рдбрд┐рдЬрд╛рдЗрди рдХрд░рдиреЗ рдХрд╛ рдХреЛрдИ рдЕрдиреБрднрд╡ рдирд╣реАрдВ рдерд╛, рдЗрд╕рд▓рд┐рдП рд╕рд╛рдордЧреНрд░реА рдХрд╛ рдЕрдзреНрдпрдпрди рдХрд░рдиреЗ рдФрд░ OOP рдХреЗ рд╕рд╛рде рд╕реЛрдЪрдиреЗ рдХреЗ рд▓рд┐рдП рджреЛ рджрд┐рди рд▓рдЧ рдЧрдП, рджреГрд╖реНрдЯрд┐рдХреЛрдг рдореЗрдВ рдЦреБрд╢реА рдХреЗ рд▓рд┐рдП рдПрдХ рджрд┐рди, рдФрд░ рдПрдХ рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЛ рд╡рд┐рдХрд╕рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдпрд╛ рджреЛ рджрд┐рди, рдпреВрдирд┐рдЯреА рд╕реЗ рд▓рдбрд╝рдирд╛, рдмрд╛рд▓ рдФрд░ рдзреБрдПрдВ рдХреЗ рдирдореВрдиреЗ рдирд┐рдХрд╛рд▓рдирд╛ ред рд▓реЗрдЦ рдореЗрдВ рдереЛрдбрд╝рд╛ рд╕рд┐рджреНрдзрд╛рдВрдд рдФрд░ рдПрдХ рдЫреЛрдЯрд╛ рд╕рд╛ рдЙрджрд╛рд╣рд░рдг рдкрд░рд┐рдпреЛрдЬрдирд╛ рд╣реИред


рдИрд╕реАрдПрд╕ рдХрд╛ рдЕрд░реНрде рдХрд╛рдлреА рд╕рд░рд▓ рд╣реИ - рдПрдХ рдЗрдХрд╛рдИ ( рдЗрдХрд╛рдИ ) рдЬрд┐рд╕рдХреЗ рдШрдЯрдХреЛрдВ ( рдШрдЯрдХ ) рдХреЗ рд╕рд╛рде рд╕рд┐рд╕реНрдЯрдо ( рд╕рд┐рд╕реНрдЯрдо ) рджреНрд╡рд╛рд░рд╛ рд╕рдВрд╕рд╛рдзрд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред

рд╕рд╛рд░


рдЗрдХрд╛рдИ рдХреЗ рдкрд╛рд╕ рдХреЛрдИ рддрд░реНрдХ рдирд╣реАрдВ рд╣реИ рдФрд░ рдХреЗрд╡рд▓ рдШрдЯрдХреЛрдВ рдХреЛ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рддрд╛ рд╣реИ (рдкреБрд░рд╛рдиреЗ CPC рджреГрд╖реНрдЯрд┐рдХреЛрдг рдореЗрдВ GameObject рдХреЗ рд╕рдорд╛рди)ред рдпреВрдирд┐рдЯреА ECS рдореЗрдВ, рдЗрдХрд╛рдИ рд╡рд░реНрдЧ рдЗрд╕рдХреЗ рд▓рд┐рдП рдореМрдЬреВрдж рд╣реИред

рдЕрдВрдЧ


рдЕрд╡рдпрд╡ рдХреЗрд╡рд▓ рдбрд╛рдЯрд╛ рд╕реНрдЯреЛрд░, рдФрд░ рдХрднреА рдХрднреА рд╕рдм рдкрд░ рдХреБрдЫ рднреА рд╢рд╛рдорд┐рд▓ рдирд╣реАрдВ рд╣реИ, рдФрд░ рдмрд╕ рд╕рдВрд╕рд╛рдзрди рдкреНрд░рдгрд╛рд▓реА рдХреЗ рд▓рд┐рдП рдПрдХ рдорд╛рд░реНрдХрд░ рд╣реИред рд▓реЗрдХрд┐рди рд╡реЗ рдХрд┐рд╕реА рднреА рддрд░реНрдХ рдирд╣реАрдВ рд╣реИред ComponentDataWrapper рд╕реЗ рдирд┐рд╣рд┐рддред рдЗрд╕реЗ рджреВрд╕рд░реЗ рдзрд╛рдЧреЗ рджреНрд╡рд╛рд░рд╛ рд╕рдВрд╕рд╛рдзрд┐рдд рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ (рд▓реЗрдХрд┐рди рдПрдХ рдЕрддрд┐ рд╕реВрдХреНрд╖реНрдо рдЕрдВрддрд░ рд╣реИ)ред

рдкреНрд░рдгрд╛рд▓реА


рдпрд╣ рднреА рд╕рд┐рд╕реНрдЯрдо рдШрдЯрдХреЛрдВ рдХреЗ рдкреНрд░рд╕рдВрд╕реНрдХрд░рдг рдХреЗ рд▓рд┐рдП рдЬрд┐рдореНрдореЗрджрд╛рд░ рд╣реИред рдЗрдирдкреБрдЯ рд╡реЗ рдПрдХрддрд╛ рдХреА рд╕реВрдЪреА рд╕реЗ рдкреНрд░рд╛рдкреНрдд рдШрдЯрдХреЛрдВ рдкреНрд░рдХрд╛рд░ рдФрд░ рдЕрддрд┐рднрд╛рд░рд┐рдд рд╡рд┐рдзрд┐рдпреЛрдВ (analogues рдЕрджреНрдпрддрди, рдкреНрд░рд╛рд░рдВрдн, OnDestroy) рдЬрд╛рджреВ рдЦреЗрд▓ рдпрд╛рдВрддреНрд░рд┐рдХреА рд╣реЛрддреА рд╣реИ рдирд┐рд░реНрджрд┐рд╖реНрдЯ рд╕рдВрд╕рд╛рдзрд┐рддред ComponentSystem рдпрд╛ JobComponentSystem рд╕реЗ рдирд┐рд╣рд┐рддред

рдиреМрдХрд░реА рд╕рд┐рд╕реНрдЯрдо


рд╕рд┐рд╕реНрдЯрдо рдХреЗ рдореИрдХреЗрдирд┐рдХреНрд╕ рдЬреЛ рдШрдЯрдХреЛрдВ рдХреЗ рд╕рдорд╛рдирд╛рдВрддрд░ рдкреНрд░рд╕рдВрд╕реНрдХрд░рдг рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИред рдкреНрд░рдгрд╛рд▓реА OnUpdate-рдиреМрдХрд░реА рд╕рдВрд░рдЪрдирд╛ рдмрдирд╛рддрд╛ рд╣реИ рдФрд░ рдкреНрд░рд╕рдВрд╕реНрдХрд░рдг рдХреЛ рдЬреЛрдбрд╝рд╛ рдЧрдпрд╛ред рдКрдм рдФрд░ рдореБрдХреНрдд рд╕рдВрд╕рд╛рдзрдиреЛрдВ рдХреЗ рдПрдХ рдХреНрд╖рдг рдореЗрдВ, рдПрдХрддрд╛ рдШрдЯрдХреЛрдВ рдХреЛ рд╕рдВрд╕рд╛рдзрд┐рдд рдФрд░ рд▓рд╛рдЧреВ рдХрд░реЗрдЧреАред

рдорд▓реНрдЯреАрдереНрд░реЗрдбрд┐рдВрдЧ рдФрд░ рдпреВрдирд┐рдЯреА 2018


рд╕рднреА рдЬреЙрдм рд╕рд┐рд╕реНрдЯрдо рдХрд╛ рдХрд╛рдо рджреВрд╕рд░реЗ рдереНрд░реЗрдбреНрд╕ рдореЗрдВ рд╣реЛрддрд╛ рд╣реИ, рдФрд░ рд╕реНрдЯреИрдВрдбрд░реНрдб рдХрдВрдкреЛрдиреЗрдВрдЯреНрд╕ (рдЯреНрд░рд╛рдВрд╕рдлреЙрд░реНрдо, рд░рд┐рдЧрд┐рдбреАрдмреЙрдбреА рдЗрддреНрдпрд╛рджрд┐) рдХреЛ рдореЗрди рдереНрд░реЗрдб рдХреЗ рдЕрд▓рд╛рд╡рд╛ рдХрд┐рд╕реА рднреА рдереНрд░реЗрдб рдореЗрдВ рдирд╣реАрдВ рдмрджрд▓рд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред рдЗрд╕рд▓рд┐рдП, рдорд╛рдирдХ рдкреИрдХреЗрдЬ рдореЗрдВ рд╕рдВрдЧрдд "рдкреНрд░рддрд┐рд╕реНрдерд╛рдкрди" рдШрдЯрдХ рд╣реЛрддреЗ рд╣реИрдВ - рд╕реНрдерд┐рддрд┐ рдШрдЯрдХ, рд░реЛрдЯреЗрд╢рди рдШрдЯрдХ, рдореЗрд╖ рдЖрд╡реГрддреНрддрд┐ рд░реЗрдВрдбрд░рд░ рдШрдЯрдХред

рд╡рд╣реА рд╡реЗрдХреНрдЯрд░ 3 рдпрд╛ рдХреНрд╡рд╛рдЯрд░рдирд┐рдпрди рдЬреИрд╕реА рдорд╛рдирдХ рд╕рдВрд░рдЪрдирд╛рдУрдВ рдкрд░ рд▓рд╛рдЧреВ рд╣реЛрддрд╛ рд╣реИред рд╕рдорд╛рдирддрд╛ рдХреЗ рд▓рд┐рдП рдШрдЯрдХ рдХреЗрд╡рд▓ рд╕рд░рд▓рддрдо рдбреЗрдЯрд╛ рдкреНрд░рдХрд╛рд░реЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВ (рдлреНрд▓реЛрдЯ 3, рдлреНрд▓реЛрдЯ 4, рдпрд╣ рд╕рдм рд╣реИ, рдЧреНрд░рд╛рдлрд┐рдХреНрд╕ рдкреНрд░реЛрдЧреНрд░рд╛рдорд░ рдЦреБрд╢ рд╣реЛрдВрдЧреЗ), рдПрдХрддрд╛ рдореЗрдВ рдЬреЛрдбрд╝рд╛ рдЧрдпрд╛ред рдореИрдереЗрдореЗрдЯрд┐рдХреНрд╕ рдирд╛рдорд╕реНрдерд╛рди, рдЙрдиреНрд╣реЗрдВ рд╕рдВрд╕рд╛рдзрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдЧрдгрд┐рдд рд╡рд░реНрдЧ рднреА рд╣реИред рдХреЛрдИ рддрд╛рд░ рдирд╣реАрдВ, рдХреЛрдИ рд╕рдВрджрд░реНрдн рдкреНрд░рдХрд╛рд░ рдирд╣реАрдВ, рдХреЗрд╡рд▓ рдХрдЯреНрдЯрд░ред

"рдореБрдЭреЗ рдХреЛрдб рджрд┐рдЦрд╛рдПрдВ"


рддреЛ, рдХреБрдЫ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░рдиреЗ рдХрд╛ рд╕рдордп!

рдПрдХ рдШрдЯрдХ рдмрдирд╛рдПрдВ рдЬреЛ рдЧрддрд┐ рдорд╛рди рдХреЛ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рддрд╛ рд╣реИ, рдФрд░ рд╕рд┐рд╕реНрдЯрдо рдХреЗ рд▓рд┐рдП рдорд╛рд░реНрдХрд░реЛрдВ рдореЗрдВ рд╕реЗ рдПрдХ рднреА рд╣реИ рдЬреЛ рдСрдмреНрдЬреЗрдХреНрдЯреНрд╕ рдХреЛ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░рддрд╛ рд╣реИред Serializable рд╡рд┐рд╢реЗрд╖рддрд╛ рдЖрдкрдХреЛ рдирд┐рд░реАрдХреНрд╖рдХ рдореЗрдВ рдорд╛рди рд╕реЗрдЯ рдФрд░ рдЯреНрд░реИрдХ рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддреА рд╣реИред

SpeedComponent
[Serializable] public struct SpeedData : IComponentData { public int Value; } public class SpeedComponent : ComponentDataWrapper<SpeedData> {} 


рдЗрдВрдЬреЗрдХреНрдЯ рд╡рд┐рд╢реЗрд╖рддрд╛ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реБрдП, рд╕рд┐рд╕реНрдЯрдо рдПрдХ рд╕рдВрд░рдЪрдирд╛ рдкреНрд░рд╛рдкреНрдд рдХрд░рддрд╛ рд╣реИ рдЬрд┐рд╕рдореЗрдВ рдХреЗрд╡рд▓ рдЙрди рд╕рдВрд╕реНрдерд╛рдУрдВ рдХреЗ рдШрдЯрдХ рд╣реЛрддреЗ рд╣реИрдВ рдЬрд┐рди рдкрд░ рд╕рднреА рддреАрди рдШрдЯрдХ рдореМрдЬреВрдж рд╣реЛрддреЗ рд╣реИрдВред рдЗрд╕рд▓рд┐рдП, рдпрджрд┐ рдХреБрдЫ рдЗрдХрд╛рдИ рдореЗрдВ рд╕реНрдерд┐рддрд┐рдХрдВрдкреЛрдиреЗрдВрдЯ рдФрд░ рд╕реНрдкреАрдбрдХрдВрдкреЛрдиреЗрдВрдЯ рдШрдЯрдХ рд╣реИрдВ, рд▓реЗрдХрд┐рди рд░реЛрдЯреЗрд╢рдирдХрдВрдкреЛрдиреЗрдВрдЯ рдирд╣реАрдВ рд╣реИ, рддреЛ рдЗрд╕ рдЗрдХрд╛рдИ рдХреЛ рд╕рд┐рд╕реНрдЯрдо рдореЗрдВ рдкреНрд░рд╡реЗрд╢ рдХрд░рдиреЗ рд╡рд╛рд▓реА рд╕рдВрд░рдЪрдирд╛ рдореЗрдВ рдирд╣реАрдВ рдЬреЛрдбрд╝рд╛ рдЬрд╛рдПрдЧрд╛ред рдЗрд╕ рдкреНрд░рдХрд╛рд░, рдпрд╣ рдШрдЯрдХ рд╕рдВрд╕реНрдерд╛рдУрдВ рдХреА рдореМрдЬреВрджрдЧреА рд╕реЗ рдлрд╝рд┐рд▓реНрдЯрд░ рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ред

MovementSystem
 public class MovementSystem : ComponentSystem { public struct ShipsPositions { public int Length; public ComponentDataArray<Position> Positions; public ComponentDataArray<Rotation> Rotations; public ComponentDataArray<SpeedData> Speeds; } [Inject] ShipsPositions _shipsMovementData; protected override void OnUpdate() { for(int i = 0; i < _shipsMovementData.Length; i++) { _shipsMovementData.Positions[i] = new Position(_shipsMovementData.Positions[i].Value + math.forward(_shipsMovementData.Rotations[i].Value) * Time.deltaTime * _shipsMovementData.Speeds[i].Value); } } } 


рдЕрдм рдЗрди рддреАрди рдШрдЯрдХреЛрдВ рд╡рд╛рд▓реЗ рд╕рднреА рдСрдмреНрдЬреЗрдХреНрдЯ рдПрдХ рдирд┐рд╢реНрдЪрд┐рдд рдЧрддрд┐ рд╕реЗ рдЖрдЧреЗ рдмрдврд╝реЗрдВрдЧреЗред

Uiiiii


рдпрд╣ рдЖрд╕рд╛рди рдерд╛ред рд╣рд╛рд▓рд╛рдВрдХрд┐ ECS рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рд╕реЛрдЪрдиреЗ рдореЗрдВ рдПрдХ рджрд┐рди рд▓рдЧ рдЧрдпрд╛ред

рд▓реЗрдХрд┐рди рд░реБрдХрд┐рдПред рдпрд╣рд╛рдБ рдиреМрдХрд░реА рдкреНрд░рдгрд╛рд▓реА рдХрд╣рд╛рдБ рд╣реИ?

рддрдереНрдп рдпрд╣ рд╣реИ рдХрд┐ рдорд▓реНрдЯреАрдереНрд░реЗрдбрд┐рдВрдЧ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХреБрдЫ рднреА рдирд╣реАрдВ рддреЛрдбрд╝рд╛ рдЧрдпрд╛ рд╣реИред рдмреНрд░реЗрдХ рдХрд░рдиреЗ рдХрд╛ рд╕рдордп!

рдореИрдВрдиреЗ рдЙрди рдирдореВрдиреЛрдВ рд╕реЗ рдЦреАрдВрдЪ рд▓рд┐рдпрд╛ рдЬреЛ рд╕рд┐рд╕реНрдЯрдо рдкреНрд░реАрдлреИрдм рдХреЛ рдЬрдиреНрдо рджреЗрддрд╛ рд╣реИред рджрд┐рд▓рдЪрд╕реНрдк рд╕реЗ - рдпрд╣рд╛рдБ рдХреЛрдб рдХрд╛ рдПрдХ рдЯреБрдХрдбрд╝рд╛ рд╣реИ:

Spawner
 EntityManager.Instantiate(prefab, entities); for (int i = 0; i < count; i++) { var position = new Position { Value = spawnPositions[i] }; EntityManager.SetComponentData(entities[i], position); EntityManager.SetComponentData(entities[i], new SpeedData { Value = Random.Range(15, 25) }); } 


рддреЛ, рдЪрд▓реЛ 1000 рдСрдмреНрдЬреЗрдХреНрдЯреНрд╕ рдбрд╛рд▓рддреЗ рд╣реИрдВред рдЕрднреА рднреА GPU рдкрд░ рддреНрд╡рд░рд┐рдд рдЬрд╛рд▓ рдХреЗ рд▓рд┐рдП рдмрд╣реБрдд рдЕрдЪреНрдЫрд╛ рд╣реИред 5000 - рднреА рд▓рдЧрднрдЧред рдореИрдВ рджрд┐рдЦрд╛рдКрдВрдЧрд╛ рдХрд┐ 50,000 рд╡рд╕реНрддреБрдУрдВ рдХреЗ рд╕рд╛рде рдХреНрдпрд╛ рд╣реЛрддрд╛ рд╣реИред

рдЗрдХрд╛рдИ рдбрд┐рдмрдЧрд░ рдпреВрдирд┐рдЯреА рдореЗрдВ рджрд┐рдЦрд╛рдИ рджреА рд╣реИ, рдЬрд┐рд╕рдореЗрдВ рджрд┐рдЦрд╛рдпрд╛ рдЧрдпрд╛ рд╣реИ рдХрд┐ рдкреНрд░рддреНрдпреЗрдХ рд╕рд┐рд╕реНрдЯрдо рдХрд┐рддрдиреЗ рдПрдордПрд╕ рд▓реЗрддрд╛ рд╣реИред рд╕рд┐рд╕реНрдЯрдо рдХреЛ рд░рдирдЯрд╛рдЗрдо рдореЗрдВ рджрд╛рдИрдВ рдУрд░ / рдмрдВрдж рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ, рдпрд╣ рджреЗрдЦрдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐ рд╡реЗ рдХрд┐рди рд╡рд╕реНрддреБрдУрдВ рдХреЛ рд╕рдВрд╕рд╛рдзрд┐рдд рдХрд░рддреЗ рд╣реИрдВ, рд╕рд╛рдорд╛рдиреНрдп рддреМрд░ рдкрд░, рдПрдХ рдЕрдкреВрд░рдгреАрдп рдЪреАрдЬред

рдРрд╕реА рд╕реНрдкреЗрд╕рд╢рд┐рдк рдмреЙрд▓ рд▓реЗрдВ


рдЙрдкрдХрд░рдг 15 рдПрдлрдкреАрдПрд╕ рдХреА рдЧрддрд┐ рд╕реЗ рд░рд┐рдХреЙрд░реНрдб рдХрд░рддрд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдкреВрд░реЗ рдмрд┐рдВрджреБ рд╕рд┐рд╕реНрдЯрдо рдХреА рд╕реВрдЪреА рдореЗрдВ рд╕рдВрдЦреНрдпрд╛рдУрдВ рдореЗрдВ рд╣реИред рд╣рдорд╛рд░рд╛, MovementSystem, рдкреНрд░рддреНрдпреЗрдХ рдлреНрд░реЗрдо рдореЗрдВ рд╕рднреА 50,000 рд╡рд╕реНрддреБрдУрдВ рдХреЛ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░рддрд╛ рд╣реИ, рдФрд░ рдпрд╣ рдФрд╕рддрди 60 рдПрдордПрд╕ рдореЗрдВ рдХрд░рддрд╛ рд╣реИред рддреЛ, рдЕрдм рдЦреЗрд▓ рдЕрдиреБрдХреВрд▓рди рдХреЗ рд▓рд┐рдП рдкрд░реНрдпрд╛рдкреНрдд рд░реВрдк рд╕реЗ рдЯреВрдЯ рдЧрдпрд╛ рд╣реИред
рднрд╛рдбрд╝ JobSystem рдЧрддрд┐ рд╡реНрдпрд╡рд╕реНрдерд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдПред

рд╕рдВрд╢реЛрдзрд┐рдд рдЖрдВрджреЛрд▓рди рдкреНрд░рдгрд╛рд▓реА
 public class MovementSystem : JobComponentSystem { [ComputeJobOptimization] struct MoveShipJob : IJobProcessComponentData<Position, Rotation, SpeedData> { public float dt; public void Execute(ref Position position, ref Rotation rotation, ref SpeedData speed) { position.Value += math.forward(rotation.Value) * dt * speed.Value; } } protected override JobHandle OnUpdate(JobHandle inputDeps) { var job = new MoveShipJob { dt = Time.deltaTime }; return job.Schedule(this, 1, inputDeps); } } 


рдЕрдм рдкреНрд░рдгрд╛рд▓реА рдкреНрд░рддреНрдпреЗрдХ рдлреНрд░реЗрдо рдореЗрдВ JobComponentSystem рд╕реЗ рд▓реА рдЧрдИ рд╣реИ рдФрд░ рдПрдХ рд╡рд┐рд╢реЗрд╖ рд╣реИрдВрдбрд▓рд░, рдЬреЛ рдПрдХ рд╣реА рдПрдХрддрд╛ 3 deltaTime рдШрдЯрдХ рдФрд░ рдкреНрд░рдгрд╛рд▓реА рдкрд╣реБрдВрдЪрд╛рддрд╛ рдЙрддреНрдкрдиреНрди рдХрд░рддрд╛ рд╣реИред

рдлрд┐рд░ рд╕реЗ рд╕реНрдкреЗрд╕рд╢рд┐рдк рд▓реЙрдиреНрдЪ рдХрд░реЗрдВ


реж.резрел рдПрдордПрд╕ (реж.рек рдЪрд░рдо рдкрд░, рд╣рд╛рдБ) рдмрдирд╛рдо релреж- atреж! 50 рд╣рдЬрд╛рд░ рд╡рд╕реНрддреБрдПрдВ! рдореИрдВрдиреЗ рдХреИрд▓рдХреБрд▓реЗрдЯрд░ рдореЗрдВ рдЗрди рдирдВрдмрд░реЛрдВ рдХреЛ рджрд░реНрдЬ рдХрд┐рдпрд╛, рдЬрд╡рд╛рдм рдореЗрдВ рдЙрд╕рдиреЗ рдПрдХ рдЦреБрд╢ рдЪреЗрд╣рд░рд╛ рджрд┐рдЦрд╛рдпрд╛ред

рдкреНрд░рдмрдВрдз


рдЖрдк рдЕрдВрддрд╣реАрди рд░реВрдк рд╕реЗ рдПрдХ рдЙрдбрд╝рдиреЗ рд╡рд╛рд▓реА рдЧреЗрдВрдж рдХреЛ рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ, рдпрд╛ рдЖрдк рдЬрд╣рд╛рдЬреЛрдВ рдХреЗ рдмреАрдЪ рдЙрдбрд╝ рд╕рдХрддреЗ рд╣реИрдВред
рдПрдХ рдЯреИрдХреНрд╕реА рдкреНрд░рдгрд╛рд▓реА рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред

рд░реЛрдЯреЗрд╢рди рдШрдЯрдХ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдкреНрд░реАрдлреИрдм рдкрд░ рд╣реИ, рднрдВрдбрд╛рд░рдг рдирд┐рдпрдВрддреНрд░рдг рдХреЗ рд▓рд┐рдП рдПрдХ рдШрдЯрдХ рдмрдирд╛рдПрдВред

ControlComponent
 [Serializable] public struct RotationControlData : IComponentData { public float roll; public float pitch; public float yaw; } public class ControlComponent : ComponentDataWrapper<RotationControlData>{} 


рд╣рдореЗрдВ рдПрдХ рдЦрд┐рд▓рд╛рдбрд╝реА рдШрдЯрдХ рдХреА рднреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ (рд╣рд╛рд▓рд╛рдБрдХрд┐ рдпрд╣ рдПрдХ рд╕рд╛рде рд╕рднреА 50k рдЬрд╣рд╛рдЬреЛрдВ рдХреЛ рдЪрд▓рд╛рдиреЗ рдХреА рд╕рдорд╕реНрдпрд╛ рдирд╣реАрдВ рд╣реИ)

PlayerComponent
 public struct PlayerData : IComponentData { } public class PlayerComponent : ComponentDataWrapper<PlayerData> { } 


рдФрд░ рддреБрд░рдВрдд, рдПрдХ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдЗрдирдкреБрдЯ рд░реАрдбрд░ред

UserControlSystem
 public class UserControlSystem : ComponentSystem { public struct InputPlayerData { public int Length; [ReadOnly] public ComponentDataArray<PlayerData> Data; public ComponentDataArray<RotationControlData> Controls; } [Inject] InputPlayerData _playerData; protected override void OnUpdate() { for (int i = 0; i < _playerData.Length; i++) { _playerData.Controls[i] = new RotationControlData { roll = Input.GetAxis("Horizontal"), pitch = Input.GetAxis("Vertical"), yaw = Input.GetKey(KeyCode.Q) ? -1 : Input.GetKey(KeyCode.E) ? 1 : 0 }; } } } 


рдорд╛рдирдХ рдЗрдирдкреБрдЯ рдХреЗ рдмрдЬрд╛рдп, рдХреЛрдИ рднреА рдкрд╕рдВрджреАрджрд╛ рд╕рд╛рдЗрдХрд┐рд▓ рдпрд╛ AI рд╣реЛ рд╕рдХрддрд╛ рд╣реИред

рдФрд░ рдЕрдВрдд рдореЗрдВ, рдкреНрд░рд╕рдВрд╕реНрдХрд░рдг рдирд┐рдпрдВрддреНрд░рдг рдФрд░ рдЦреБрдж рдХреЛ рдмрджрд▓ рджреЗрддрд╛ рд╣реИред рдореБрдЭреЗ рдЗрд╕ рддрдереНрдп рд╕реЗ рд╕рд╛рдордирд╛ рдХрд░рдирд╛ рдкрдбрд╝рд╛ рдХрд┐ рдореИрде.рдпреВрд▓рд░ рдЕрднреА рддрдХ рд▓рд╛рдЧреВ рдирд╣реАрдВ рд╣реБрдЖ рд╣реИ, рдЗрд╕рд▓рд┐рдП рд╡рд┐рдХрд┐рдкреАрдбрд┐рдпрд╛ рдкрд░ рдПрдХ рддреНрд╡рд░рд┐рдд рдЫрд╛рдкреЗ рдиреЗ рдореБрдЭреЗ рдпреВрд▓рд░ рдХреЗ рдХреЛрдиреЛрдВ рд╕реЗ рдЪрддреБрд░реНрднреБрдЬ рдореЗрдВ рд░реВрдкрд╛рдВрддрд░рдг рд╕реЗ рдмрдЪрд╛рдпрд╛ред

ProcessRotationInputSystem
 public class ProcessRotationInputSystem : JobComponentSystem { struct LocalRotationSpeedGroup { public ComponentDataArray<Rotation> rotations; [ReadOnly] public ComponentDataArray<RotationSpeedData> rotationSpeeds; [ReadOnly] public ComponentDataArray<RotationControlData> controlData; public int Length; } [Inject] private LocalRotationSpeedGroup _rotationGroup; [ComputeJobOptimization] struct RotateJob : IJobParallelFor { public ComponentDataArray<Rotation> rotations; [ReadOnly] public ComponentDataArray<RotationSpeedData> rotationSpeeds; [ReadOnly] public ComponentDataArray<RotationControlData> controlData; public float dt; public void Execute(int i) { var speed = rotationSpeeds[i].Value; if (speed > 0.0f) { quaternion nRotation = math.normalize(rotations[i].Value); float yaw = controlData[i].yaw * speed * dt; float pitch = controlData[i].pitch * speed * dt; float roll = -controlData[i].roll * speed * dt; quaternion result = math.mul(nRotation, Euler(pitch, roll, yaw)); rotations[i] = new Rotation { Value = result }; } } quaternion Euler(float roll, float yaw, float pitch) { float cy = math.cos(yaw * 0.5f); float sy = math.sin(yaw * 0.5f); float cr = math.cos(roll * 0.5f); float sr = math.sin(roll * 0.5f); float cp = math.cos(pitch * 0.5f); float sp = math.sin(pitch * 0.5f); float qw = cy * cr * cp + sy * sr * sp; float qx = cy * sr * cp - sy * cr * sp; float qy = cy * cr * sp + sy * sr * cp; float qz = sy * cr * cp - cy * sr * sp; return new quaternion(qx, qy, qz, qw); } } protected override JobHandle OnUpdate(JobHandle inputDeps) { var job = new RotateJob { rotations = _rotationGroup.rotations, rotationSpeeds = _rotationGroup.rotationSpeeds, controlData = _rotationGroup.controlData, dt = Time.deltaTime }; return job.Schedule(_rotationGroup.Length, 64, inputDeps); } } ] .Value); public class ProcessRotationInputSystem : JobComponentSystem { struct LocalRotationSpeedGroup { public ComponentDataArray<Rotation> rotations; [ReadOnly] public ComponentDataArray<RotationSpeedData> rotationSpeeds; [ReadOnly] public ComponentDataArray<RotationControlData> controlData; public int Length; } [Inject] private LocalRotationSpeedGroup _rotationGroup; [ComputeJobOptimization] struct RotateJob : IJobParallelFor { public ComponentDataArray<Rotation> rotations; [ReadOnly] public ComponentDataArray<RotationSpeedData> rotationSpeeds; [ReadOnly] public ComponentDataArray<RotationControlData> controlData; public float dt; public void Execute(int i) { var speed = rotationSpeeds[i].Value; if (speed > 0.0f) { quaternion nRotation = math.normalize(rotations[i].Value); float yaw = controlData[i].yaw * speed * dt; float pitch = controlData[i].pitch * speed * dt; float roll = -controlData[i].roll * speed * dt; quaternion result = math.mul(nRotation, Euler(pitch, roll, yaw)); rotations[i] = new Rotation { Value = result }; } } quaternion Euler(float roll, float yaw, float pitch) { float cy = math.cos(yaw * 0.5f); float sy = math.sin(yaw * 0.5f); float cr = math.cos(roll * 0.5f); float sr = math.sin(roll * 0.5f); float cp = math.cos(pitch * 0.5f); float sp = math.sin(pitch * 0.5f); float qw = cy * cr * cp + sy * sr * sp; float qx = cy * sr * cp - sy * cr * sp; float qy = cy * cr * sp + sy * sr * cp; float qz = sy * cr * cp - cy * sr * sp; return new quaternion(qx, qy, qz, qw); } } protected override JobHandle OnUpdate(JobHandle inputDeps) { var job = new RotateJob { rotations = _rotationGroup.rotations, rotationSpeeds = _rotationGroup.rotationSpeeds, controlData = _rotationGroup.controlData, dt = Time.deltaTime }; return job.Schedule(_rotationGroup.Length, 64, inputDeps); } } ); public class ProcessRotationInputSystem : JobComponentSystem { struct LocalRotationSpeedGroup { public ComponentDataArray<Rotation> rotations; [ReadOnly] public ComponentDataArray<RotationSpeedData> rotationSpeeds; [ReadOnly] public ComponentDataArray<RotationControlData> controlData; public int Length; } [Inject] private LocalRotationSpeedGroup _rotationGroup; [ComputeJobOptimization] struct RotateJob : IJobParallelFor { public ComponentDataArray<Rotation> rotations; [ReadOnly] public ComponentDataArray<RotationSpeedData> rotationSpeeds; [ReadOnly] public ComponentDataArray<RotationControlData> controlData; public float dt; public void Execute(int i) { var speed = rotationSpeeds[i].Value; if (speed > 0.0f) { quaternion nRotation = math.normalize(rotations[i].Value); float yaw = controlData[i].yaw * speed * dt; float pitch = controlData[i].pitch * speed * dt; float roll = -controlData[i].roll * speed * dt; quaternion result = math.mul(nRotation, Euler(pitch, roll, yaw)); rotations[i] = new Rotation { Value = result }; } } quaternion Euler(float roll, float yaw, float pitch) { float cy = math.cos(yaw * 0.5f); float sy = math.sin(yaw * 0.5f); float cr = math.cos(roll * 0.5f); float sr = math.sin(roll * 0.5f); float cp = math.cos(pitch * 0.5f); float sp = math.sin(pitch * 0.5f); float qw = cy * cr * cp + sy * sr * sp; float qx = cy * sr * cp - sy * cr * sp; float qy = cy * cr * sp + sy * sr * cp; float qz = sy * cr * cp - cy * sr * sp; return new quaternion(qx, qy, qz, qw); } } protected override JobHandle OnUpdate(JobHandle inputDeps) { var job = new RotateJob { rotations = _rotationGroup.rotations, rotationSpeeds = _rotationGroup.rotationSpeeds, controlData = _rotationGroup.controlData, dt = Time.deltaTime }; return job.Schedule(_rotationGroup.Length, 64, inputDeps); } } ); public class ProcessRotationInputSystem : JobComponentSystem { struct LocalRotationSpeedGroup { public ComponentDataArray<Rotation> rotations; [ReadOnly] public ComponentDataArray<RotationSpeedData> rotationSpeeds; [ReadOnly] public ComponentDataArray<RotationControlData> controlData; public int Length; } [Inject] private LocalRotationSpeedGroup _rotationGroup; [ComputeJobOptimization] struct RotateJob : IJobParallelFor { public ComponentDataArray<Rotation> rotations; [ReadOnly] public ComponentDataArray<RotationSpeedData> rotationSpeeds; [ReadOnly] public ComponentDataArray<RotationControlData> controlData; public float dt; public void Execute(int i) { var speed = rotationSpeeds[i].Value; if (speed > 0.0f) { quaternion nRotation = math.normalize(rotations[i].Value); float yaw = controlData[i].yaw * speed * dt; float pitch = controlData[i].pitch * speed * dt; float roll = -controlData[i].roll * speed * dt; quaternion result = math.mul(nRotation, Euler(pitch, roll, yaw)); rotations[i] = new Rotation { Value = result }; } } quaternion Euler(float roll, float yaw, float pitch) { float cy = math.cos(yaw * 0.5f); float sy = math.sin(yaw * 0.5f); float cr = math.cos(roll * 0.5f); float sr = math.sin(roll * 0.5f); float cp = math.cos(pitch * 0.5f); float sp = math.sin(pitch * 0.5f); float qw = cy * cr * cp + sy * sr * sp; float qx = cy * sr * cp - sy * cr * sp; float qy = cy * cr * sp + sy * sr * cp; float qz = sy * cr * cp - cy * sr * sp; return new quaternion(qx, qy, qz, qw); } } protected override JobHandle OnUpdate(JobHandle inputDeps) { var job = new RotateJob { rotations = _rotationGroup.rotations, rotationSpeeds = _rotationGroup.rotationSpeeds, controlData = _rotationGroup.controlData, dt = Time.deltaTime }; return job.Schedule(_rotationGroup.Length, 64, inputDeps); } } 


рдЖрдк рд╢рд╛рдпрдж рдпрд╣ рдкреВрдЫреЗрдВрдЧреЗ рдХрд┐ рдЖрдк рдЬреЙрдм рдореЗрдВ рд╕рд┐рд░реНрдл 3 рдШрдЯрдХреЛрдВ рдХреЛ рдкрд╛рд╕ рдХреНрдпреЛрдВ рдирд╣реАрдВ рдХрд░ рд╕рдХрддреЗ, рдЬреИрд╕рд╛ рдХрд┐ рдореВрд╡рдореЗрдВрдЯрд╕рд┐рд╕реНрдЯрдо рдореЗрдВ рд╣реИ? рдХреНрдпреЛрдВрдХрд┐ред рдореИрдВ рд▓рдВрдмреЗ рд╕рдордп рддрдХ рдЗрд╕рд╕реЗ рдЬреВрдЭрддрд╛ рд░рд╣рд╛, рд▓реЗрдХрд┐рди рдореБрдЭреЗ рдирд╣реАрдВ рдкрддрд╛ рдХрд┐ рдпрд╣ рдХреНрдпреЛрдВ рдХрд╛рдо рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИред рдирдореВрдиреЛрдВ рдореЗрдВ, рдШрдЯрдХ CompataDataArray рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рд┐рдд рдХрд┐рдП рдЬрд╛рддреЗ рд╣реИрдВ, рд▓реЗрдХрд┐рди рд╣рдо рдХреИрдирди рд╕реЗ рд╡рд╛рдкрд╕ рдирд╣реАрдВ рдЖрдПрдВрдЧреЗред

рд╣рдо рдордВрдЪ рдкрд░ рдкреНрд░реАрдлрд╝реИрдм рдлреЗрдВрдХрддреЗ рд╣реИрдВ, рдШрдЯрдХреЛрдВ рдХреЛ рд▓рдЯрдХрд╛рддреЗ рд╣реИрдВ, рдХреИрдорд░рд╛ рдЯрд╛рдИ рдХрд░рддреЗ рд╣реИрдВ, рдмреЛрд░рд┐рдВрдЧ рд╡реЙрд▓рдкреЗрдкрд░ рд╕реЗрдЯ рдХрд░рддреЗ рд╣реИрдВ, рдФрд░ рдЬрд╛рддреЗ рд╣реИрдВ!



рдирд┐рд╖реНрдХрд░реНрд╖


рдПрдХрддрд╛ рдЯреЗрдХреНрдиреЛрд▓реЙрдЬреАрдЬ рдХреЗ рд▓реЛрдЧ рдорд▓реНрдЯреАрдереНрд░реЗрдбрд┐рдВрдЧ рдХреА рд╕рд╣реА рджрд┐рд╢рд╛ рдореЗрдВ рдЪрд▓реЗ рдЧрдП рд╣реИрдВред рдЬреЙрдм рд╕рд┐рд╕реНрдЯрдо рдЕрднреА рднреА рдирдо рд╣реИ (рдЕрд▓реНрдлрд╛ рд╕рдВрд╕реНрдХрд░рдг рд╕рдм рдХреЗ рдмрд╛рдж рд╣реИ), рд▓реЗрдХрд┐рди рдпрд╣ рдХрд╛рдлреА рдкреНрд░рдпреЛрдЧ рдХрд░рдиреЗ рдпреЛрдЧреНрдп рд╣реИ рдФрд░ рдЕрдм рддреЗрдЬреА рд▓рд╛ рд░рд╣рд╛ рд╣реИред рджреБрд░реНрднрд╛рдЧреНрдп рд╕реЗ, рдорд╛рдирдХ рдШрдЯрдХ рдЬреЙрдм рд╕рд┐рд╕реНрдЯрдо рдХреЗ рд╕рд╛рде рд╕рдВрдЧрдд рдирд╣реАрдВ рд╣реИрдВ (рд▓реЗрдХрд┐рди рдЕрд▓рдЧ рд╕реЗ рдИрд╕реАрдПрд╕ рдХреЗ рд╕рд╛рде рдирд╣реАрдВ!), рдЗрд╕рд▓рд┐рдП рдЖрдкрдХреЛ рдЗрд╕реЗ рдкрд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдмреИрд╕рд╛рдЦреА рдХреЛ рддрд░рд╛рд╢рдирд╛ рд╣реЛрдЧрд╛ред рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдПрдХрддрд╛ рдордВрдЪ рдХреЗ рд╕рд╛рде рдПрдХ рд╡реНрдпрдХреНрддрд┐ GPU рдХреЗ рд▓рд┐рдП рд╢рд╛рд░реАрд░рд┐рдХ рдкреНрд░рдгрд╛рд▓реА рдХрд╛ рдПрд╣рд╕рд╛рд╕ рд╣реИ, рдФрд░, рдЬреИрд╕реЗ рдПрдХ рддрд░рд╣ рд╕реЗ, рдкреНрд░рдЧрддрд┐ рдХрд░ рд░рд╣рд╛ рд╣реИред
ECS рд╡рд┐рдж рдпреВрдирд┐рдЯреА рдХрд╛ рдЙрдкрдпреЛрдЧ рдкрд╣рд▓реЗ рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛, рдХрдИ рд╕рдореГрджреНрдз рдПрдирд╛рд▓реЙрдЧ рд╣реИрдВ, рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рд╕рдмрд╕реЗ рдкреНрд░рд╕рд┐рджреНрдз рдХреЗ рдЕрд╡рд▓реЛрдХрди рдХреЗ рд╕рд╛рде рдПрдХ рд▓реЗрдЦ ред рдпрд╣ рд╡рд╛рд╕реНрддреБрдХрд▓рд╛ рдХреЗ рд▓рд┐рдП рдЗрд╕ рджреГрд╖реНрдЯрд┐рдХреЛрдг рдХреЗ рдкреЗрд╢реЗрд╡рд░реЛрдВ рдФрд░ рд╡рд┐рдкрдХреНрд╖реЛрдВ рдХрд╛ рднреА рд╡рд░реНрдгрди рдХрд░рддрд╛ рд╣реИред

рдЕрдкрдиреЗ рдЖрдк рд╕реЗ, рдореИрдВ рдЗрд╕ рддрд░рд╣ рдХреЗ рдкреНрд▓рд╕ рдХреЛ рдХреЛрдб рдХреА рд╢реБрджреНрдзрддрд╛ рдХреЗ рд░реВрдк рдореЗрдВ рдЬреЛрдбрд╝ рд╕рдХрддрд╛ рд╣реВрдВред рдореИрдВрдиреЗ рдПрдХ рдкреНрд░рдгрд╛рд▓реА рдореЗрдВ рдЖрдВрджреЛрд▓рди рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░рдХреЗ рд╢реБрд░реБрдЖрдд рдХреАред рдШрдЯрдХреЛрдВ рдХреА рд╕рдВрдЦреНрдпрд╛, рдирд┐рд░реНрднрд░рддрд╛ рддреЗрдЬреА рд╕реЗ рд╡реГрджреНрдзрд┐ рд╣реБрдИ рд╣реИ, рдФрд░ рдореИрдВ рдЫреЛрдЯрд╛ рдФрд░ рд╕реБрд╡рд┐рдзрд╛рдЬрдирдХ рдкреНрд░рдгрд╛рд▓реА рдореЗрдВ рдХреЛрдб рдХреЛ рд╡рд┐рднрд╛рдЬрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐рдпрд╛ рдерд╛ред рдФрд░ рдЙрдиреНрд╣реЗрдВ рдЖрд╕рд╛рдиреА рд╕реЗ рдХрд┐рд╕реА рдЕрдиреНрдп рдкрд░рд┐рдпреЛрдЬрдирд╛ рдореЗрдВ рдкреБрди: рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред

рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЛрдб рдпрд╣рд╛рдБ рд╣реИ: GitHub

Source: https://habr.com/ru/post/hi413897/


All Articles