<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>UnityEngine Archives - 어제와 내일의 나 그 사이의 이야기</title>
	<atom:link href="https://lycos7560.com/tag/unityengine/feed/" rel="self" type="application/rss+xml" />
	<link></link>
	<description>생각의 흐름을 타고 다니며 만드는 블로그</description>
	<lastBuildDate>Fri, 24 Jan 2025 21:53:54 +0000</lastBuildDate>
	<language>ko-KR</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>

<image>
	<url>https://lycos7560.com/wp-content/uploads/2022/11/cropped-cropped-cropped-log-1-150x150-1-80x80.png</url>
	<title>UnityEngine Archives - 어제와 내일의 나 그 사이의 이야기</title>
	<link></link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>DOTS 정리 (2) &#8211; Unity Job 시스템 시작하기</title>
		<link>https://lycos7560.com/unity/dots-%ec%a0%95%eb%a6%ac-2-unity-job-%ec%8b%9c%ec%8a%a4%ed%85%9c-%ec%8b%9c%ec%9e%91%ed%95%98%ea%b8%b0/39257/</link>
					<comments>https://lycos7560.com/unity/dots-%ec%a0%95%eb%a6%ac-2-unity-job-%ec%8b%9c%ec%8a%a4%ed%85%9c-%ec%8b%9c%ec%9e%91%ed%95%98%ea%b8%b0/39257/#comments</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Thu, 23 Jan 2025 22:38:38 +0000</pubDate>
				<category><![CDATA[Unity]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[DOTS]]></category>
		<category><![CDATA[job]]></category>
		<category><![CDATA[Job 시스템]]></category>
		<category><![CDATA[UnityEngine]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=39257</guid>

					<description><![CDATA[<p>https://learn.unity.com/tutorial/660ffc54edbc2a1b0887d446?uv=6&#38;projectId=660ffcd3edbc2a162b7baa27#66ceb506edbc2a03a1163ee9 학습 참고 문서 공식 Sample Project 링크 https://github.com/Unity-Technologies/EntityComponentSystemSamples/tree/master 멀티코어 CPU를 활용하기 위해 Job을 사용하는 기본적인 방법 STEP &#8211; 싱글 스레드 잡을 생성, 예약, 완료 &#8211; 병렬 잡을 생성, 예약, 완료 &#8211; 다른 잡에 종속되는 잡을 예약 &#8211; NativeArrays를 사용 파란 큐브와 빨간 큐브를 각각 Seeker와 Target으로 정하고 각Seeker은 가장 가까운 Target으로 이어지는 디버그 라인을 [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/unity/dots-%ec%a0%95%eb%a6%ac-2-unity-job-%ec%8b%9c%ec%8a%a4%ed%85%9c-%ec%8b%9c%ec%9e%91%ed%95%98%ea%b8%b0/39257/">DOTS 정리 (2) &#8211; Unity Job 시스템 시작하기</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><a href="https://learn.unity.com/tutorial/660ffc54edbc2a1b0887d446?uv=6&amp;projectId=660ffcd3edbc2a162b7baa27#66ceb506edbc2a03a1163ee9" target="_blank" rel="noreferrer noopener">https://learn.unity.com/tutorial/660ffc54edbc2a1b0887d446?uv=6&amp;projectId=660ffcd3edbc2a162b7baa27#66ceb506edbc2a03a1163ee9</a></p>



<p class="wp-block-paragraph"><strong>학습 참고 문서</strong></p>



<div class="wp-block-uagb-container uagb-block-c93b07c4 alignfull uagb-is-root-container"><div class="uagb-container-inner-blocks-wrap">
<div class="wp-block-uagb-container uagb-block-7114ba86">
<figure class="wp-block-embed is-type-wp-embed"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="k304MdJ465"><a href="https://lycos7560.com/unity/dots-%ec%a0%95%eb%a6%ac-1/39221/">DOTS 정리  (1)</a></blockquote><iframe class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;DOTS 정리  (1)&#8221; &#8212; 어제와 내일의 나 그 사이의 이야기" src="https://lycos7560.com/unity/dots-%ec%a0%95%eb%a6%ac-1/39221/embed/#?secret=JiNPinxDsq#?secret=k304MdJ465" data-secret="k304MdJ465" width="600" height="338" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>
</div>



<div class="wp-block-uagb-container uagb-block-7b0cb647">
<figure class="wp-block-embed is-type-wp-embed"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="J5kMaXzq5B"><a href="https://lycos7560.com/unity/unity-manual-job-system-2023-3-%eb%b2%88%ec%97%ad/37818/">Unity Manual / Job system (2023.3) 번역</a></blockquote><iframe class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;Unity Manual / Job system (2023.3) 번역&#8221; &#8212; 어제와 내일의 나 그 사이의 이야기" src="https://lycos7560.com/unity/unity-manual-job-system-2023-3-%eb%b2%88%ec%97%ad/37818/embed/#?secret=5THfcSVN1e#?secret=J5kMaXzq5B" data-secret="J5kMaXzq5B" width="600" height="338" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>
</div>
</div></div>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph"><strong>공식 Sample Project 링크</strong></p>



<p class="wp-block-paragraph"><a href="https://github.com/Unity-Technologies/EntityComponentSystemSamples/tree/master" target="_blank" rel="noreferrer noopener">https://github.com/Unity-Technologies/EntityComponentSystemSamples/tree/master</a></p>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-wide" style="margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20)"/>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>


				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-7ee3e609      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							학습 목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#멀티코어-cpu를-활용하기-위해-job을-사용하는-기본적인-방법" class="uagb-toc-link__trigger">멀티코어 CPU를 활용하기 위해 Job을 사용하는 기본적인 방법</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#step-1-scene-no-job" class="uagb-toc-link__trigger">Step 1. Scene &#8211; No Job</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#step-2-scene-singlethreadedjob" class="uagb-toc-link__trigger">Step 2. Scene &#8211; SingleThreadedJob</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#step-3-scene-paralleljob" class="uagb-toc-link__trigger">Step 3. Scene &#8211; ParallelJob</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#step-4-scene-paralleljob-sorting" class="uagb-toc-link__trigger">Step 4. Scene &#8211; ParallelJob_Sorting</a></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">멀티코어 CPU를 활용하기 위해 Job을 사용하는 기본적인 방법</h2>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">STEP</p>



<p class="wp-block-paragraph">&#8211; 싱글 스레드 잡을 생성, 예약, 완료</p>



<p class="wp-block-paragraph">&#8211; 병렬 잡을 생성, 예약, 완료</p>



<p class="wp-block-paragraph">&#8211; 다른 잡에 종속되는 잡을 예약</p>



<p class="wp-block-paragraph">&#8211; NativeArrays를 사용</p>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">파란 큐브와 빨간 큐브를 각각 Seeker와 Target으로 정하고  각Seeker은 가장 가까운 Target으로 이어지는 디버그 라인을 추가합니다.</p>



<p class="wp-block-paragraph">각 Step은 서로 다른 방식으로 문제를 해결하여 Step마다 성능 비교를 할 수 있습니다.</p>



<p class="wp-block-paragraph">SeekerPrefab의 인스턴스를 생성 -&gt; 2D 평면을 따라 무작위 방향과 무작위 위치를 설정 -&gt; </p>



<p class="wp-block-paragraph">Target도 동일한 방식으로 생성 -&gt; 모든 Seeker와 Target의 트랜스폼을 정적 배열에 할당하여</p>



<p class="wp-block-paragraph">FindNearest 스크립트에서 액세스하여 Update()</p>



<figure class="wp-block-image size-full is-resized"><img fetchpriority="high" decoding="async" width="581" height="655" src="https://lycos7560.com/wp-content/uploads/2025/01/image-34.png" alt="" class="wp-image-39258" style="width:422px;height:auto" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-34.png 581w, https://lycos7560.com/wp-content/uploads/2025/01/image-34-266x300.png 266w" sizes="(max-width: 581px) 100vw, 581px" /><figcaption class="wp-element-caption">Sample 위치</figcaption></figure>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Step 1. Scene &#8211; No Job</h3>



<p class="wp-block-paragraph">일반적인 브루트 포스 순회 방법, 각 Seeker 마다 모든 Target과의 거리를 확인</p>



<p class="wp-block-paragraph">Spawner.cs</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using UnityEngine;

namespace Tutorials.Jobs.Step1
{
    public class Spawner : MonoBehaviour
    {
        // The set of targets is fixed, so rather than 
        // retrieve the targets every frame, we'll cache 
        // their transforms in this field.
        public static Transform[] TargetTransforms;

        public GameObject SeekerPrefab;
        public GameObject TargetPrefab;
        public int NumSeekers;
        public int NumTargets;
        public Vector2 Bounds;

        public void Start()
        {
            Random.InitState(123);

            for (int i = 0; i &lt; NumSeekers; i++)
            {
                GameObject go = GameObject.Instantiate(SeekerPrefab);
                Seeker seeker = go.GetComponent&lt;Seeker>();
                Vector2 dir = Random.insideUnitCircle;
                seeker.Direction = new Vector3(dir.x, 0, dir.y);
                go.transform.localPosition = new Vector3(
                    Random.Range(0, Bounds.x), 0, Random.Range(0, Bounds.y));
            }

            TargetTransforms = new Transform[NumTargets];
            for (int i = 0; i &lt; NumTargets; i++)
            {
                GameObject go = GameObject.Instantiate(TargetPrefab);
                Target target = go.GetComponent&lt;Target>();
                Vector2 dir = Random.insideUnitCircle;
                target.Direction = new Vector3(dir.x, 0, dir.y);
                TargetTransforms[i] = go.transform;
                go.transform.localPosition = new Vector3(
                    Random.Range(0, Bounds.x), 0, Random.Range(0, Bounds.y));
            }
        }
    }
}</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">Seeker.cs</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using UnityEngine;

namespace Tutorials.Jobs.Step1
{
    public class Seeker : MonoBehaviour
    {
        public Vector3 Direction;

        public void Update()
        {
            transform.localPosition += Direction * Time.deltaTime;
        }
    }
}</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">Target.cs</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using UnityEngine;

namespace Tutorials.Jobs.Step1
{
    public class Target : MonoBehaviour
    {
        public Vector3 Direction;

        public void Update()
        {
            transform.localPosition += Direction * Time.deltaTime;
        }
    }
}</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">FindNearest.cs</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using UnityEngine;

namespace Tutorials.Jobs.Step1
{
    public class FindNearest : MonoBehaviour
    {
        public void Update()
        {
            // Find nearest Target.
            // When comparing distances, it's cheaper to compare
            // the squares of the distances because doing so
            // avoids computing square roots.
            Vector3 nearestTargetPosition = default;
            float nearestDistSq = float.MaxValue;
            foreach (var targetTransform in Spawner.TargetTransforms)
            {
                Vector3 offset = targetTransform.localPosition - transform.localPosition;
                float distSq = offset.sqrMagnitude;
                if (distSq &lt; nearestDistSq)
                {
                    nearestDistSq = distSq;
                    nearestTargetPosition = targetTransform.localPosition;
                }
            }

            Debug.DrawLine(transform.localPosition, nearestTargetPosition);
        }
    }
}
</pre>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="1708" height="800" src="https://lycos7560.com/wp-content/uploads/2025/01/image-39.png" alt="" class="wp-image-39263" style="width:1010px;height:auto" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-39.png 1708w, https://lycos7560.com/wp-content/uploads/2025/01/image-39-300x141.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-39-768x360.png 768w, https://lycos7560.com/wp-content/uploads/2025/01/image-39-1536x719.png 1536w" sizes="(max-width: 1708px) 100vw, 1708px" /><figcaption class="wp-element-caption">탐색시마다 FindNearest의 컴포넌트</figcaption></figure>



<figure class="wp-block-image size-full"><img decoding="async" width="493" height="161" src="https://lycos7560.com/wp-content/uploads/2025/01/image-37.png" alt="" class="wp-image-39261" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-37.png 493w, https://lycos7560.com/wp-content/uploads/2025/01/image-37-300x98.png 300w" sizes="(max-width: 493px) 100vw, 493px" /></figure>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="1480" height="888" src="https://lycos7560.com/wp-content/uploads/2025/01/image-35.png" alt="" class="wp-image-39259" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-35.png 1480w, https://lycos7560.com/wp-content/uploads/2025/01/image-35-300x180.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-35-768x461.png 768w" sizes="(max-width: 1480px) 100vw, 1480px" /><figcaption class="wp-element-caption">약 10 FPS의 성능을 보여준다.</figcaption></figure>



<figure class="wp-block-image size-full"><img decoding="async" width="1618" height="816" src="https://lycos7560.com/wp-content/uploads/2025/01/image-36.png" alt="" class="wp-image-39260" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-36.png 1618w, https://lycos7560.com/wp-content/uploads/2025/01/image-36-300x151.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-36-768x387.png 768w, https://lycos7560.com/wp-content/uploads/2025/01/image-36-1536x775.png 1536w" sizes="(max-width: 1618px) 100vw, 1618px" /></figure>



<p class="wp-block-paragraph">Seeker의 1000개의 FindNearest가 Update()를 수행하면서 약 82ms를 프레임마다 실행 (개당 0.082ms)</p>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Step 2. Scene &#8211; SingleThreadedJob</h3>



<p class="wp-block-paragraph">일반적인 브루트 포스 순회 방법, 하나의 FindNearest에서 Target과의 거리를 확인</p>



<figure class="wp-block-image size-full"><img decoding="async" width="598" height="574" src="https://lycos7560.com/wp-content/uploads/2025/01/image-38.png" alt="" class="wp-image-39262" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-38.png 598w, https://lycos7560.com/wp-content/uploads/2025/01/image-38-300x288.png 300w" sizes="(max-width: 598px) 100vw, 598px" /><figcaption class="wp-element-caption">Step2 위치</figcaption></figure>



<figure class="wp-block-image size-full"><img decoding="async" width="1049" height="581" src="https://lycos7560.com/wp-content/uploads/2025/01/image-40.png" alt="" class="wp-image-39264" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-40.png 1049w, https://lycos7560.com/wp-content/uploads/2025/01/image-40-300x166.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-40-768x425.png 768w" sizes="(max-width: 1049px) 100vw, 1049px" /></figure>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">Spawner.cs</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using UnityEngine;
using Seeker = Tutorials.Jobs.Step1.Seeker;
using Target = Tutorials.Jobs.Step1.Target;

namespace Tutorials.Jobs.Step2
{
    public class Spawner : MonoBehaviour
    {
        public static Transform[] TargetTransforms;

        // Cache the seeker transforms.
        public static Transform[] SeekerTransforms;

        public GameObject SeekerPrefab;
        public GameObject TargetPrefab;
        public int NumSeekers;
        public int NumTargets;
        public Vector2 Bounds;

        public void Start()
        {
            Random.InitState(123);

            SeekerTransforms = new Transform[NumSeekers];
            for (int i = 0; i &lt; NumSeekers; i++)
            {
                GameObject go = GameObject.Instantiate(SeekerPrefab);
                Seeker seeker = go.GetComponent&lt;Seeker>();
                Vector2 dir = Random.insideUnitCircle;
                seeker.Direction = new Vector3(dir.x, 0, dir.y);
                SeekerTransforms[i] = go.transform;
                go.transform.localPosition = new Vector3(
                    Random.Range(0, Bounds.x), 0, Random.Range(0, Bounds.y));
            }

            TargetTransforms = new Transform[NumTargets];
            for (int i = 0; i &lt; NumTargets; i++)
            {
                GameObject go = GameObject.Instantiate(TargetPrefab);
                Target target = go.GetComponent&lt;Target>();
                Vector2 dir = Random.insideUnitCircle;
                target.Direction = new Vector3(dir.x, 0, dir.y);
                TargetTransforms[i] = go.transform;
                go.transform.localPosition = new Vector3(
                    Random.Range(0, Bounds.x), 0, Random.Range(0, Bounds.y));
            }
        }
    }
}</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">FindNearest.cs</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;

namespace Tutorials.Jobs.Step2
{
    public class FindNearest : MonoBehaviour
    {
        // The size of our arrays does not need to vary, so rather than create
        // new arrays every field, we'll create the arrays in Awake() and store them
        // in these fields.
        NativeArray&lt;float3> TargetPositions;
        NativeArray&lt;float3> SeekerPositions;
        NativeArray&lt;float3> NearestTargetPositions;

        public void Start()
        {
            Spawner spawner = Object.FindFirstObjectByType&lt;Spawner>();
            // We use the Persistent allocator because these arrays must
            // exist for the run of the program.
            TargetPositions = new NativeArray&lt;float3>(spawner.NumTargets, Allocator.Persistent);
            SeekerPositions = new NativeArray&lt;float3>(spawner.NumSeekers, Allocator.Persistent);
            NearestTargetPositions = new NativeArray&lt;float3>(spawner.NumSeekers, Allocator.Persistent);
        }

        // We are responsible for disposing of our allocations
        // when we no longer need them.
        public void OnDestroy()
        {
            TargetPositions.Dispose();
            SeekerPositions.Dispose();
            NearestTargetPositions.Dispose();
        }

        public void Update()
        {
            // Copy every target transform to a NativeArray.
            for (int i = 0; i &lt; TargetPositions.Length; i++)
            {
                // Vector3 is implicitly converted to float3
                TargetPositions[i] = Spawner.TargetTransforms[i].localPosition;
            }

            // Copy every seeker transform to a NativeArray.
            for (int i = 0; i &lt; SeekerPositions.Length; i++)
            {
                // Vector3 is implicitly converted to float3
                SeekerPositions[i] = Spawner.SeekerTransforms[i].localPosition;
            }

            // To schedule a job, we first need to create an instance and populate its fields.
            FindNearestJob findJob = new FindNearestJob
            {
                TargetPositions = TargetPositions,
                SeekerPositions = SeekerPositions,
                NearestTargetPositions = NearestTargetPositions,
            };

            // Schedule() puts the job instance on the job queue.
            JobHandle findHandle = findJob.Schedule();

            // The Complete method will not return until the job represented by
            // the handle finishes execution. Effectively, the main thread waits
            // here until the job is done.
            findHandle.Complete();

            // Draw a debug line from each seeker to its nearest target.
            for (int i = 0; i &lt; SeekerPositions.Length; i++)
            {
                // float3 is implicitly converted to Vector3
                Debug.DrawLine(SeekerPositions[i], NearestTargetPositions[i]);
            }
        }
    }
}
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">FindNearestJob.cs</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;

// We'll use Unity.Mathematics.float3 instead of Vector3,
// and we'll use Unity.Mathematics.math.distancesq instead of Vector3.sqrMagnitude.
using Unity.Mathematics;

namespace Tutorials.Jobs.Step2
{
    // Include the BurstCompile attribute to Burst compile the job.
    [BurstCompile]
    public struct FindNearestJob : IJob
    {
        // All of the data which a job will access should 
        // be included in its fields. In this case, the job needs
        // three arrays of float3.

        // Array and collection fields that are only read in
        // the job should be marked with the ReadOnly attribute.
        // Although not strictly necessary in this case, marking data  
        // as ReadOnly may allow the job scheduler to safely run 
        // more jobs concurrently with each other.
        // (See the "Intro to jobs" for more detail.)

        [ReadOnly] public NativeArray&lt;float3> TargetPositions;
        [ReadOnly] public NativeArray&lt;float3> SeekerPositions;

        // For SeekerPositions[i], we will assign the nearest 
        // target position to NearestTargetPositions[i].
        public NativeArray&lt;float3> NearestTargetPositions;

        // 'Execute' is the only method of the IJob interface.
        // When a worker thread executes the job, it calls this method.
        public void Execute()
        {
            // Compute the square distance from each seeker to every target.
            for (int i = 0; i &lt; SeekerPositions.Length; i++)
            {
                float3 seekerPos = SeekerPositions[i];
                float nearestDistSq = float.MaxValue;
                for (int j = 0; j &lt; TargetPositions.Length; j++)
                {
                    float3 targetPos = TargetPositions[j];
                    float distSq = math.distancesq(seekerPos, targetPos);
                    if (distSq &lt; nearestDistSq)
                    {
                        nearestDistSq = distSq;
                        NearestTargetPositions[i] = targetPos;
                    }
                }
            }
        }
    }
}</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="1731" height="859" src="https://lycos7560.com/wp-content/uploads/2025/01/image-41.png" alt="" class="wp-image-39265" style="width:919px;height:auto" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-41.png 1731w, https://lycos7560.com/wp-content/uploads/2025/01/image-41-300x149.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-41-768x381.png 768w, https://lycos7560.com/wp-content/uploads/2025/01/image-41-1536x762.png 1536w" sizes="(max-width: 1731px) 100vw, 1731px" /></figure>



<figure class="wp-block-image size-full"><img decoding="async" width="1503" height="951" src="https://lycos7560.com/wp-content/uploads/2025/01/image-42.png" alt="" class="wp-image-39266" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-42.png 1503w, https://lycos7560.com/wp-content/uploads/2025/01/image-42-300x190.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-42-768x486.png 768w" sizes="(max-width: 1503px) 100vw, 1503px" /><figcaption class="wp-element-caption">약 30FPS 성능</figcaption></figure>



<figure class="wp-block-image size-full"><img decoding="async" width="1282" height="1050" src="https://lycos7560.com/wp-content/uploads/2025/01/image-43.png" alt="" class="wp-image-39267" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-43.png 1282w, https://lycos7560.com/wp-content/uploads/2025/01/image-43-300x246.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-43-768x629.png 768w" sizes="(max-width: 1282px) 100vw, 1282px" /><figcaption class="wp-element-caption">하나의 FindNearest 객체에서 관리, 프레임마다 22.77ms, Worker Therad는 Idle</figcaption></figure>



<figure class="wp-block-image size-full"><img decoding="async" width="1536" height="1043" src="https://lycos7560.com/wp-content/uploads/2025/01/image-47.png" alt="" class="wp-image-39271" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-47.png 1536w, https://lycos7560.com/wp-content/uploads/2025/01/image-47-300x204.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-47-768x522.png 768w" sizes="(max-width: 1536px) 100vw, 1536px" /><figcaption class="wp-element-caption">JobHandle.Complete가 21.45ms인 이유는 Complete 호출이 반환되려면 <strong>실제 잡이 실행 및 완료될 것을 기다려야 하기 때문임</strong></figcaption></figure>



<p class="wp-block-paragraph">FindNearest 업데이트 자체는 아무것도 하지 않고 잡이 실행 및 완료될 것을 기다리고 있음</p>



<figure class="wp-block-image size-full"><img decoding="async" width="490" height="313" src="https://lycos7560.com/wp-content/uploads/2025/01/image-44.png" alt="" class="wp-image-39268" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-44.png 490w, https://lycos7560.com/wp-content/uploads/2025/01/image-44-300x192.png 300w" sizes="(max-width: 490px) 100vw, 490px" /><figcaption class="wp-element-caption">Burst는 아직 미사용이라서 Job이 메인 스레드에서 실행 중</figcaption></figure>



<p class="wp-block-paragraph">Step 1 보다 성능이 향상된 원인은 Step 1의 Seeker의 Update() 1000개에 대한 오버헤드가 발생되지 않음 (오직 1개)</p>



<p class="wp-block-paragraph">트랜스폼을 순회할 때 메모리를 건너 뛰면서 트랜스폼에 액세스했는데 이는<strong> 캐시에 부하주는 작업</strong>을 배열로 변경하여 성능을 향상시킴</p>



<figure class="wp-block-image size-full"><img decoding="async" width="776" height="794" src="https://lycos7560.com/wp-content/uploads/2025/01/image-45.png" alt="" class="wp-image-39273" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-45.png 776w, https://lycos7560.com/wp-content/uploads/2025/01/image-45-293x300.png 293w, https://lycos7560.com/wp-content/uploads/2025/01/image-45-768x786.png 768w" sizes="(max-width: 776px) 100vw, 776px" /></figure>



<figure class="wp-block-image size-full"><img decoding="async" width="626" height="712" src="https://lycos7560.com/wp-content/uploads/2025/01/image-46.png" alt="" class="wp-image-39274" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-46.png 626w, https://lycos7560.com/wp-content/uploads/2025/01/image-46-264x300.png 264w" sizes="(max-width: 626px) 100vw, 626px" /><figcaption class="wp-element-caption">Job에 BurstCompile 속성이 추가되었는데 BurstCompile될 수 있음을 뜻함</figcaption></figure>



<figure class="wp-block-image size-full"><img decoding="async" width="431" height="214" src="https://lycos7560.com/wp-content/uploads/2025/01/image-48.png" alt="" class="wp-image-39275" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-48.png 431w, https://lycos7560.com/wp-content/uploads/2025/01/image-48-300x149.png 300w" sizes="(max-width: 431px) 100vw, 431px" /><figcaption class="wp-element-caption">활성화</figcaption></figure>



<figure class="wp-block-image size-full"><img decoding="async" width="1920" height="926" src="https://lycos7560.com/wp-content/uploads/2025/01/image-49.jpg" alt="" class="wp-image-39276" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-49.jpg 1920w, https://lycos7560.com/wp-content/uploads/2025/01/image-49-300x145.jpg 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-49-768x370.jpg 768w, https://lycos7560.com/wp-content/uploads/2025/01/image-49-1536x741.jpg 1536w" sizes="(max-width: 1920px) 100vw, 1920px" /><figcaption class="wp-element-caption">30-> 102 FPS로 엄청난 성능 향상을 할 수 있음</figcaption></figure>



<figure class="wp-block-image size-full"><img decoding="async" width="1840" height="1059" src="https://lycos7560.com/wp-content/uploads/2025/01/image-49-1.jpg" alt="" class="wp-image-39277" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-49-1.jpg 1840w, https://lycos7560.com/wp-content/uploads/2025/01/image-49-1-300x173.jpg 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-49-1-768x442.jpg 768w, https://lycos7560.com/wp-content/uploads/2025/01/image-49-1-1536x884.jpg 1536w" sizes="(max-width: 1840px) 100vw, 1840px" /></figure>



<figure class="wp-block-image size-full"><img decoding="async" width="765" height="306" src="https://lycos7560.com/wp-content/uploads/2025/01/image-49.png" alt="" class="wp-image-39278" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-49.png 765w, https://lycos7560.com/wp-content/uploads/2025/01/image-49-300x120.png 300w" sizes="(max-width: 765px) 100vw, 765px" /><figcaption class="wp-element-caption">JobHandle.Complete가 21.45ms -> 1.48ms로 단축됨 (유니티에서는 약 20배의 성능 향상이 있다고함)</figcaption></figure>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Step 3. Scene &#8211; ParallelJob</h3>



<p class="wp-block-paragraph">일반적인 브루트 포스 순회 방법, 하나의 FindNearest에서 Target과의 거리를 확인</p>



<figure class="wp-block-image size-full"><img decoding="async" width="749" height="499" src="https://lycos7560.com/wp-content/uploads/2025/01/image-50.png" alt="" class="wp-image-39279" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-50.png 749w, https://lycos7560.com/wp-content/uploads/2025/01/image-50-300x200.png 300w" sizes="(max-width: 749px) 100vw, 749px" /></figure>



<figure class="wp-block-image size-full"><img decoding="async" width="1056" height="568" src="https://lycos7560.com/wp-content/uploads/2025/01/image-51.png" alt="" class="wp-image-39280" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-51.png 1056w, https://lycos7560.com/wp-content/uploads/2025/01/image-51-300x161.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-51-768x413.png 768w" sizes="(max-width: 1056px) 100vw, 1056px" /><figcaption class="wp-element-caption">Step 1, 2는 각 1,000개 Step 3는 각 5000개의 인스턴스를 사용</figcaption></figure>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">FindNearest.cs</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Spawner = Tutorials.Jobs.Step2.Spawner;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;

namespace Tutorials.Jobs.Step3
{
    // Exactly the same as in prior step except this schedules a parallel job instead of a single-threaded job.
    public class FindNearest : MonoBehaviour
    {
        NativeArray&lt;float3> TargetPositions;
        NativeArray&lt;float3> SeekerPositions;
        NativeArray&lt;float3> NearestTargetPositions;

        public void Start()
        {
            Spawner spawner = GetComponent&lt;Spawner>();
            TargetPositions = new NativeArray&lt;float3>(spawner.NumTargets, Allocator.Persistent);
            SeekerPositions = new NativeArray&lt;float3>(spawner.NumSeekers, Allocator.Persistent);
            NearestTargetPositions = new NativeArray&lt;float3>(spawner.NumSeekers, Allocator.Persistent);
        }

        public void OnDestroy()
        {
            TargetPositions.Dispose();
            SeekerPositions.Dispose();
            NearestTargetPositions.Dispose();
        }

        public void Update()
        {
            for (int i = 0; i &lt; TargetPositions.Length; i++)
            {
                TargetPositions[i] = Spawner.TargetTransforms[i].localPosition;
            }

            for (int i = 0; i &lt; SeekerPositions.Length; i++)
            {
                SeekerPositions[i] = Spawner.SeekerTransforms[i].localPosition;
            }

            FindNearestJob findJob = new FindNearestJob
            {
                TargetPositions = TargetPositions,
                SeekerPositions = SeekerPositions,
                NearestTargetPositions = NearestTargetPositions,
            };

            // Execute will be called once for every element of the SeekerPositions array,
            // with every index from 0 up to (but not including) the length of the array.
            // The Execute calls will be split into batches of 100.
            JobHandle findHandle = findJob.Schedule(SeekerPositions.Length, 100);

            findHandle.Complete();

            for (int i = 0; i &lt; SeekerPositions.Length; i++)
            {
                Debug.DrawLine(SeekerPositions[i], NearestTargetPositions[i]);
            }
        }
    }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">FindNearestJob.cs</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;

namespace Tutorials.Jobs.Step3
{
    [BurstCompile]
    public struct FindNearestJob : IJobParallelFor
    {
        [ReadOnly] public NativeArray&lt;float3> TargetPositions;
        [ReadOnly] public NativeArray&lt;float3> SeekerPositions;

        public NativeArray&lt;float3> NearestTargetPositions;

        public void Execute(int index)
        {
            float3 seekerPos = SeekerPositions[index];
            float nearestDistSq = float.MaxValue;
            for (int i = 0; i &lt; TargetPositions.Length; i++)
            {
                float3 targetPos = TargetPositions[i];
                float distSq = math.distancesq(seekerPos, targetPos);
                if (distSq &lt; nearestDistSq)
                {
                    nearestDistSq = distSq;
                    NearestTargetPositions[index] = targetPos;
                }
            }
        }
    }
}</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="1920" height="652" src="https://lycos7560.com/wp-content/uploads/2025/01/image-52.jpg" alt="" class="wp-image-39281" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-52.jpg 1920w, https://lycos7560.com/wp-content/uploads/2025/01/image-52-300x102.jpg 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-52-768x261.jpg 768w, https://lycos7560.com/wp-content/uploads/2025/01/image-52-1536x522.jpg 1536w" sizes="(max-width: 1920px) 100vw, 1920px" /><figcaption class="wp-element-caption">약 31.8FPS</figcaption></figure>



<figure class="wp-block-image size-full"><img decoding="async" width="1660" height="1121" src="https://lycos7560.com/wp-content/uploads/2025/01/image-52.png" alt="" class="wp-image-39282" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-52.png 1660w, https://lycos7560.com/wp-content/uploads/2025/01/image-52-300x203.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-52-768x519.png 768w, https://lycos7560.com/wp-content/uploads/2025/01/image-52-1536x1037.png 1536w" sizes="(max-width: 1660px) 100vw, 1660px" /><figcaption class="wp-element-caption">ParallelJob을 이용하여 Job이 다른 모든 워커 스레드에 병렬적으로 분할되도록 함</figcaption></figure>



<figure class="wp-block-image size-full"><img decoding="async" width="812" height="376" src="https://lycos7560.com/wp-content/uploads/2025/01/image-54.png" alt="" class="wp-image-39284" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-54.png 812w, https://lycos7560.com/wp-content/uploads/2025/01/image-54-300x139.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-54-768x356.png 768w" sizes="(max-width: 812px) 100vw, 812px" /><figcaption class="wp-element-caption">100개의 인덱스 단위로 병렬 작업</figcaption></figure>



<figure class="wp-block-image size-full"><img decoding="async" width="1028" height="252" src="https://lycos7560.com/wp-content/uploads/2025/01/image-53.png" alt="" class="wp-image-39283" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-53.png 1028w, https://lycos7560.com/wp-content/uploads/2025/01/image-53-300x74.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-53-768x188.png 768w" sizes="(max-width: 1028px) 100vw, 1028px" /></figure>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Step 4. Scene &#8211; ParallelJob_Sorting</h3>



<p class="wp-block-paragraph">정렬을 이용한 병렬 작업</p>



<figure class="wp-block-image size-full"><img decoding="async" width="625" height="534" src="https://lycos7560.com/wp-content/uploads/2025/01/image-55.png" alt="" class="wp-image-39285" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-55.png 625w, https://lycos7560.com/wp-content/uploads/2025/01/image-55-300x256.png 300w" sizes="(max-width: 625px) 100vw, 625px" /></figure>



<figure class="wp-block-image size-full"><img decoding="async" width="1066" height="518" src="https://lycos7560.com/wp-content/uploads/2025/01/image-56.png" alt="" class="wp-image-39286" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-56.png 1066w, https://lycos7560.com/wp-content/uploads/2025/01/image-56-300x146.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-56-768x373.png 768w" sizes="(max-width: 1066px) 100vw, 1066px" /><figcaption class="wp-element-caption">각 10,000개의 인스턴스 생성</figcaption></figure>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">FindNearest.cs</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using Spawner = Tutorials.Jobs.Step2.Spawner;

namespace Tutorials.Jobs.Step4
{
    public class FindNearest : MonoBehaviour
    {
        NativeArray&lt;float3> TargetPositions;
        NativeArray&lt;float3> SeekerPositions;
        NativeArray&lt;float3> NearestTargetPositions;

        public void Start()
        {
            Spawner spawner = GetComponent&lt;Spawner>();
            TargetPositions = new NativeArray&lt;float3>(spawner.NumTargets, Allocator.Persistent);
            SeekerPositions = new NativeArray&lt;float3>(spawner.NumSeekers, Allocator.Persistent);
            NearestTargetPositions = new NativeArray&lt;float3>(spawner.NumSeekers, Allocator.Persistent);
        }

        public void OnDestroy()
        {
            TargetPositions.Dispose();
            SeekerPositions.Dispose();
            NearestTargetPositions.Dispose();
        }

        public void Update()
        {
            for (int i = 0; i &lt; TargetPositions.Length; i++)
            {
                TargetPositions[i] = Spawner.TargetTransforms[i].localPosition;
            }

            for (int i = 0; i &lt; SeekerPositions.Length; i++)
            {
                SeekerPositions[i] = Spawner.SeekerTransforms[i].localPosition;
            }

            SortJob&lt;float3, AxisXComparer> sortJob = TargetPositions.SortJob(new AxisXComparer { });

            FindNearestJob findJob = new FindNearestJob
            {
                TargetPositions = TargetPositions,
                SeekerPositions = SeekerPositions,
                NearestTargetPositions = NearestTargetPositions,
            };

            JobHandle sortHandle = sortJob.Schedule();
            JobHandle findHandle = findJob.Schedule(SeekerPositions.Length, 100, sortHandle);
            findHandle.Complete();

            for (int i = 0; i &lt; SeekerPositions.Length; i++)
            {
                Debug.DrawLine(SeekerPositions[i], NearestTargetPositions[i]);
            }
        }
    }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">FindNearestJob.cs</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using System.Collections.Generic;
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;

namespace Tutorials.Jobs.Step4
{
    [BurstCompile]
    public struct FindNearestJob : IJobParallelFor
    {
        [ReadOnly] public NativeArray&lt;float3> TargetPositions;
        [ReadOnly] public NativeArray&lt;float3> SeekerPositions;

        public NativeArray&lt;float3> NearestTargetPositions;

        public void Execute(int index)
        {
            float3 seekerPos = SeekerPositions[index];

            // Find the target with the closest X coord.
            int startIdx = TargetPositions.BinarySearch(seekerPos, new AxisXComparer { });

            // When no precise match is found, BinarySearch returns the bitwise negation of the last-searched offset.
            // So when startIdx is negative, we flip the bits again, but we then must ensure the index is within bounds.
            if (startIdx &lt; 0) startIdx = ~startIdx;
            if (startIdx >= TargetPositions.Length) startIdx = TargetPositions.Length - 1;

            // The position of the target with the closest X coord.
            float3 nearestTargetPos = TargetPositions[startIdx];
            float nearestDistSq = math.distancesq(seekerPos, nearestTargetPos);

            // Searching upwards through the array for a closer target.
            Search(seekerPos, startIdx + 1, TargetPositions.Length, +1, ref nearestTargetPos, ref nearestDistSq);

            // Search downwards through the array for a closer target.
            Search(seekerPos, startIdx - 1, -1, -1, ref nearestTargetPos, ref nearestDistSq);

            NearestTargetPositions[index] = nearestTargetPos;
        }

        void Search(float3 seekerPos, int startIdx, int endIdx, int step,
                    ref float3 nearestTargetPos, ref float nearestDistSq)
        {
            for (int i = startIdx; i != endIdx; i += step)
            {
                float3 targetPos = TargetPositions[i];
                float xdiff = seekerPos.x - targetPos.x;

                // If the square of the x distance is greater than the current nearest, we can stop searching.
                if ((xdiff * xdiff) > nearestDistSq) break;

                float distSq = math.distancesq(targetPos, seekerPos);

                if (distSq &lt; nearestDistSq)
                {
                    nearestDistSq = distSq;
                    nearestTargetPos = targetPos;
                }
            }
        }
    }

    public struct AxisXComparer : IComparer&lt;float3>
    {
        public int Compare(float3 a, float3 b)
        {
            return a.x.CompareTo(b.x);
        }
    }
}
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="931" height="651" src="https://lycos7560.com/wp-content/uploads/2025/01/image-57.png" alt="" class="wp-image-39287" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-57.png 931w, https://lycos7560.com/wp-content/uploads/2025/01/image-57-300x210.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-57-768x537.png 768w" sizes="(max-width: 931px) 100vw, 931px" /></figure>



<p class="wp-block-paragraph">x축을 따라 TargetPositions를 정렬합니다</p>



<p class="wp-block-paragraph">이어서 FindNearestJob에서 바이너리 검색을 통해 탐색기의 x 좌표와 가장 가까운 타겟의 위치를 찾고</p>



<p class="wp-block-paragraph">그 인덱스 주위를 탐색해 가장 가까운 타겟을 찾습니다. </p>



<p class="wp-block-paragraph">그러면 대부분의 경우 탐색기를 모든 타겟과 비교하는 대신 몇몇 타겟과만 비교하는 작업이 되어 더 간단합니다.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="348" height="32" src="https://lycos7560.com/wp-content/uploads/2025/01/image-60.png" alt="" class="wp-image-39290" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-60.png 348w, https://lycos7560.com/wp-content/uploads/2025/01/image-60-300x28.png 300w" sizes="(max-width: 348px) 100vw, 348px" /></figure>



<p class="wp-block-paragraph">먼저 SortJob은 Collections 패키지의 네이티브 배열 확장 메서드로서 SortJob 구조체를 반환합니다.</p>



<p class="wp-block-paragraph">그 구조체 자체는 잡이 아니지만 Schedule 메서드를 호출할 경우 하나가 아닌 <strong>두 개의 잡</strong>이 예약됩니다.</p>



<p class="wp-block-paragraph">이 메서드는 먼저 segmentSortJob을 생성하고 예약하는데 이는 배열의 서로 다른 세그먼트를 독립적으로 정렬하는 병렬 잡입니다.</p>



<p class="wp-block-paragraph">이후 두 번째 작업이자 싱글 스레드 작업이며 같은 배열 내 세그먼트들을 병합하는 SegmentSortMerge가 예약됩니다</p>



<p class="wp-block-paragraph">이 두 잡은 기존 배열 내에서 MergeSort를 수행합니다.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="1139" height="529" src="https://lycos7560.com/wp-content/uploads/2025/01/image-59.png" alt="" class="wp-image-39289" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-59.png 1139w, https://lycos7560.com/wp-content/uploads/2025/01/image-59-300x139.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-59-768x357.png 768w" sizes="(max-width: 1139px) 100vw, 1139px" /></figure>



<p class="wp-block-paragraph">결정적으로 두 번째의 병합 잡은 세그먼트 정렬이 완료될 때까지 기다려야 합니다.</p>



<p class="wp-block-paragraph">그래서 두 번째 잡을 예약할 때 첫 번째 잡의 잡 핸들을 전달하면 첫 번째 잡은 두 번째 잡의 종속 관계가 됩니다.</p>



<p class="wp-block-paragraph">잡이 종속 관계를 가질 경우 워커 스레드들은 그 종속 관계의 실행이 모두 완료될 때까지 해당 잡을 실행하지 않습니다.</p>



<p class="wp-block-paragraph">둘 이상의 잡을 예약해야 하며 해당 잡들의 실행 순서를 지정해야 할 때는 종속 관계를 통해 그 순서를 정할 수 있습니다.</p>



<p class="wp-block-paragraph">이 코드에서 FindNearestJob을 예약할 때 sortJob 핸들을 종속성으로 전달하는데 이는 MergeSort가 완료될 때까지</p>



<p class="wp-block-paragraph">이 잡의 실행이 시작되어서는 안 되기 때문입니다.</p>



<p class="wp-block-paragraph">이후 잡이 완료되면 잡의 모든 종속성도 묵시적으로 완료되므로 SegmentSort, SegmentMerge, FindNearestJob의 세 잡이 모두 완료됩니다.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="796" height="98" src="https://lycos7560.com/wp-content/uploads/2025/01/image-58.png" alt="" class="wp-image-39288" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-58.png 796w, https://lycos7560.com/wp-content/uploads/2025/01/image-58-300x37.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-58-768x95.png 768w" sizes="(max-width: 796px) 100vw, 796px" /></figure>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="1065" height="552" src="https://lycos7560.com/wp-content/uploads/2025/01/image-61.png" alt="" class="wp-image-39291" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-61.png 1065w, https://lycos7560.com/wp-content/uploads/2025/01/image-61-300x155.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-61-768x398.png 768w" sizes="(max-width: 1065px) 100vw, 1065px" /></figure>



<p class="wp-block-paragraph">FindNearestJob을 보면 Collections 패키지의 네이티브 배열 확장 메서드인 <strong>BinarySearch</strong>를 호출하고 있습니다.</p>



<p class="wp-block-paragraph">이는 단지 X 좌표와 비교해서 SeekerPos와 일치하는 TargetPositions 내 인덱스를 반환합니다.</p>



<p class="wp-block-paragraph">대부분은 정확히 일치하는 결과를 찾을 수 없을 것이므로 반환되는 인덱스 값은 대부분 음수가 될 겁니다.</p>



<p class="wp-block-paragraph">하지만 이 메서드에 숨겨진 요령이 있다면 <strong>반환된 인덱스 값의 비트를 뒤집어서 배열에서 가장 가까운 일치 값의 인덱스를 얻을 수</strong> 있다는 겁니다.</p>



<p class="wp-block-paragraph">해당 값을 고정해서 범위 내에 있게 하면 됩니다.</p>



<p class="wp-block-paragraph">이제 x 좌표가 탐색기의 x 좌표와 가장 가까운 타겟의 인덱스를 알아냈지만 타겟을 위아래로 검색하여 탐색기와 더욱 가까운 타겟이 존재하지 않는지 확인합니다.</p>



<p class="wp-block-paragraph">이 검색에서 중요한 요령은 x 좌표값의 차이가 지금까지 찾아낸 최단 거리보다 더 클 경우 검색을 중단할 수 있다는 겁니다.</p>



<p class="wp-block-paragraph">TargetPositions가 x 좌표에 따라 정렬되니까요</p>



<p class="wp-block-paragraph">이 임곗값을 넘고 나면 해당 임곗값 반대편에 존재하는 모든 후보들은 우리가 이미 찾아낸 후보보다 더 가까이에 있을 수 없습니다.</p>



<figure class="wp-block-image"><img decoding="async" width="1920" height="888" src="https://lycos7560.com/wp-content/uploads/2025/01/image-62.jpg" alt="" class="wp-image-39292" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-62.jpg 1920w, https://lycos7560.com/wp-content/uploads/2025/01/image-62-300x139.jpg 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-62-768x355.jpg 768w, https://lycos7560.com/wp-content/uploads/2025/01/image-62-1536x710.jpg 1536w" sizes="(max-width: 1920px) 100vw, 1920px" /><figcaption class="wp-element-caption">31 FPS의 성능</figcaption></figure>



<figure class="wp-block-image size-full"><img decoding="async" width="1668" height="1092" src="https://lycos7560.com/wp-content/uploads/2025/01/image-62.png" alt="" class="wp-image-39294" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-62.png 1668w, https://lycos7560.com/wp-content/uploads/2025/01/image-62-300x196.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-62-768x503.png 768w, https://lycos7560.com/wp-content/uploads/2025/01/image-62-1536x1006.png 1536w" sizes="(max-width: 1668px) 100vw, 1668px" /></figure>



<figure class="wp-block-image size-full"><img decoding="async" width="1084" height="285" src="https://lycos7560.com/wp-content/uploads/2025/01/image-63.png" alt="" class="wp-image-39295" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-63.png 1084w, https://lycos7560.com/wp-content/uploads/2025/01/image-63-300x79.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-63-768x202.png 768w" sizes="(max-width: 1084px) 100vw, 1084px" /><figcaption class="wp-element-caption">SegmentSortMerge는 하나의 스레드에서만 실행</figcaption></figure>



<figure class="wp-block-image size-full"><img decoding="async" width="1327" height="497" src="https://lycos7560.com/wp-content/uploads/2025/01/image-64.png" alt="" class="wp-image-39296" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-64.png 1327w, https://lycos7560.com/wp-content/uploads/2025/01/image-64-300x112.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-64-768x288.png 768w" sizes="(max-width: 1327px) 100vw, 1327px" /><figcaption class="wp-element-caption">트랜스폼을복사하는 데 시간이 더 오래 걸림</figcaption></figure>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"></p>
<p>The post <a href="https://lycos7560.com/unity/dots-%ec%a0%95%eb%a6%ac-2-unity-job-%ec%8b%9c%ec%8a%a4%ed%85%9c-%ec%8b%9c%ec%9e%91%ed%95%98%ea%b8%b0/39257/">DOTS 정리 (2) &#8211; Unity Job 시스템 시작하기</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/unity/dots-%ec%a0%95%eb%a6%ac-2-unity-job-%ec%8b%9c%ec%8a%a4%ed%85%9c-%ec%8b%9c%ec%9e%91%ed%95%98%ea%b8%b0/39257/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>DOTS 정리  (1) &#8211; 개념 및 이론</title>
		<link>https://lycos7560.com/unity/dots-%ec%a0%95%eb%a6%ac-1/39221/</link>
					<comments>https://lycos7560.com/unity/dots-%ec%a0%95%eb%a6%ac-1/39221/#comments</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Thu, 23 Jan 2025 19:41:59 +0000</pubDate>
				<category><![CDATA[Unity]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[DOTS]]></category>
		<category><![CDATA[job]]></category>
		<category><![CDATA[Job 시스템]]></category>
		<category><![CDATA[UnityEngine]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=39221</guid>

					<description><![CDATA[<p>Unity DOTS에 대하여 Data-Oriented Technology Stack (데이터 지향 기술 스택) 개요 Unity DOTS는 하드웨어 특성과 제약 조건을 최대한 활용할 수 있도록 지원하는 소프트웨어 아키텍처 OOP(객체 지향 프로그래밍) 접근 방식과 달리 DOD(데이터 지향 디자인)를 사용하며 코드와 데이터에 중점을 둔 접근 방식 DOTS를 사용하면 멀티코어 프로세서를 활용하여 데이터 처리를 병렬화할 수 있음 기능 및 패키지 C# Job [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/unity/dots-%ec%a0%95%eb%a6%ac-1/39221/">DOTS 정리  (1) &#8211; 개념 및 이론</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-9619d28d      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							DOTS에 대한 기본 개념						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#unity-dots에-대하여" class="uagb-toc-link__trigger">Unity DOTS에 대하여</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#개요" class="uagb-toc-link__trigger">개요</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#기능-및-패키지" class="uagb-toc-link__trigger">기능 및 패키지</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#dots의-주요-개념" class="uagb-toc-link__trigger">DOTS의 주요 개념</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#종속성을-이용한-해결" class="uagb-toc-link__trigger">종속성을 이용한 해결</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#job-병렬-처리" class="uagb-toc-link__trigger">Job 병렬 처리</a></li></ul></li><li class="uagb-toc__list"><a href="#ecs-entity와-component" class="uagb-toc-link__trigger">ECS Entity와 Component</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#job과-unity-gameobject의-호환성-문제" class="uagb-toc-link__trigger">Job과 Unity GameObject의 호환성 문제</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#entity-component-system" class="uagb-toc-link__trigger">Entity Component System</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#world와-entitymanager" class="uagb-toc-link__trigger">World와 EntityManager</a></ul></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><strong>Unity DOTS</strong>에 대하여</h2>



<p class="wp-block-paragraph">Data-Oriented Technology Stack <strong>(데이터 지향 기술 스택)</strong></p>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">개요</h3>



<p class="wp-block-paragraph"><strong>Unity</strong> <strong>DOTS</strong>는 하드웨어 특성과 제약 조건을 최대한 활용할 수 있도록 지원하는 소프트웨어 아키텍처</p>



<p class="wp-block-paragraph"><strong>OOP(객체 지향 프로그래밍) </strong>접근 방식과 달리 <strong>DOD(데이터 지향 디자인)를 사용</strong>하며 코드와 데이터에 중점을 둔 접근 방식</p>



<p class="wp-block-paragraph">DOTS를 사용하면 멀티코어 프로세서를 활용하여 <strong>데이터 처리를 병렬화</strong>할 수 있음</p>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">기능 및 패키지</h3>



<p class="wp-block-paragraph"><strong>C# Job 시스템</strong><br>멀티 스레드 코드를 쉽고 안전하게 작성함으로써 최신 CPU의 수많은 코어를 활용</p>



<p class="wp-block-paragraph"><strong>버스트 컴파일러</strong><br>Mono나 ILCPP보다 훨씬 빠른 코드를 생성하는 최적화 C# 컴파일러</p>



<p class="wp-block-paragraph"><strong>Unity.Mathematics</strong> <br>버스트 컴파일된 코드에서 사용할 때 <strong>특별히 최적화된 수학 라이브러리</strong>를 제공</p>



<p class="wp-block-paragraph"><strong>Unity.Collections</strong> <br>List, HashMap 같은 일반적인 컬렉션 유형을 제공하며 버스트 컴파일된 코드에서 사용됨 <br><strong>일반 C# 컬렉션과는 다르게 관리</strong>되고 <strong>Job에서 안전하게 사용될 수 있도록 안전성 검사를 지원</strong></p>



<p class="wp-block-paragraph"><strong>Unity.Entities</strong> <br><strong>ECS(엔티티 컴포넌트 시스템) 아키텍처의 형태</strong>를 제공<br>즉, <strong><em>엔티티</em>는 게임 오브젝트보다 가볍고 효율적인 대체제이며 <em>시스템</em>이라는 코드 단위로 처리</strong>됩니다.</p>



<p class="wp-block-paragraph"><strong>Unity.Entities.Graphics</strong> <br><strong>URP(유니버설 렌더 파이프라인) </strong>또는 <strong>HDRP(고해상도 렌더 파이프라인)를 사용</strong>하여 엔티티를 렌더링</p>



<p class="wp-block-paragraph"><strong>[번외 1]</strong></p>



<p class="wp-block-paragraph"><strong>Unity.Physics</strong><br>결정론적 리지드바디 동역학 시스템과 공간 쿼리 시스템을 제공</p>



<p class="wp-block-paragraph"><strong>Unity.Netcode</strong> 패키지(<strong>Netcode for Entities</strong>라고도 함)<br>권한 서버와 클라이언트측 예측이 포함된 네트워크 멀티플레이어 기능을 제공</p>



<p class="wp-block-paragraph"><strong>[번외 2 &#8211; 20250123]</strong></p>



<p class="wp-block-paragraph">Unity에서는 현재 <strong>애니메이션, 오디오, UI</strong>에 대한 완전한 엔티티 기반 솔루션을 제공하지 않기 때문에, </p>



<p class="wp-block-paragraph">엔티티 기반 프로젝트에서 해당 기능을 구현하려면 기존의 <strong>게임 오브젝트나 다른 대안에 의존</strong>해야 합니다.</p>



<p class="wp-block-paragraph">예를 들어, 애니메이션이 적용된 몬스터가 등장하는 엔티티 기반 게임을 제작할 경우, </p>



<p class="wp-block-paragraph">시뮬레이션 로직에서는 몬스터를 엔티티로 표현하더라도 최종적으로는 게임 오브젝트를 사용해 렌더링해야 합니다.</p>



<p class="wp-block-paragraph">이를 위해 각 몬스터 엔티티와 해당 게임 오브젝트 간의 상태를 동기화해야 하며, 이러한 방식은 프로젝트 규모가 크지 않다면 허용 가능한 수준의 오버헤드로 간주될 수 있습니다.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="1475" height="509" src="https://lycos7560.com/wp-content/uploads/2025/01/image-4.png" alt="" class="wp-image-39222" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-4.png 1475w, https://lycos7560.com/wp-content/uploads/2025/01/image-4-300x104.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-4-768x265.png 768w" sizes="(max-width: 1475px) 100vw, 1475px" /><figcaption class="wp-element-caption">Entities Package &amp; Dependencies</figcaption></figure>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">DOTS의 주요 개념</h3>



<p class="wp-embed-aspect-16-9 wp-has-aspect-ratio wp-block-paragraph"><a href="https://youtu.be/jdW66hA-Qu8" target="_blank" rel="noreferrer noopener">https://youtu.be/jdW66hA-Qu8</a></p>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="1483" height="997" src="https://lycos7560.com/wp-content/uploads/2025/01/image-5.jpg" alt="" class="wp-image-39223" style="width:784px;height:auto" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-5.jpg 1483w, https://lycos7560.com/wp-content/uploads/2025/01/image-5-300x202.jpg 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-5-768x516.jpg 768w" sizes="(max-width: 1483px) 100vw, 1483px" /></figure>



<p class="wp-block-paragraph">Unity 프로파일러를 보면 Main Thread 외에도 추가적인 Worker Thread를 생성한다</p>



<figure class="wp-block-image size-full"><img decoding="async" width="1447" height="268" src="https://lycos7560.com/wp-content/uploads/2025/01/image-5.png" alt="" class="wp-image-39224" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-5.png 1447w, https://lycos7560.com/wp-content/uploads/2025/01/image-5-300x56.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-5-768x142.png 768w" sizes="(max-width: 1447px) 100vw, 1447px" /></figure>



<p class="wp-block-paragraph">예를 들어 해당 환경은 코어가 8개 있는 컴퓨터에서 실행 중이기 때문에 7개의 Worker Thread가 존재</p>



<p class="wp-block-paragraph">Worker Thread는 유니티 내부에서 사용되지만 C#의 Job System을 이용하면 Worker Thread에서도 자체 고유 코드를 실행할 수 있음</p>



<p class="wp-block-paragraph">(Job은 Worker Thread에서 실행되는 작업 단위 )</p>



<figure class="wp-block-image size-full"><img decoding="async" width="617" height="399" src="https://lycos7560.com/wp-content/uploads/2025/01/image-6.png" alt="" class="wp-image-39225" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-6.png 617w, https://lycos7560.com/wp-content/uploads/2025/01/image-6-300x194.png 300w" sizes="(max-width: 617px) 100vw, 617px" /><figcaption class="wp-element-caption">배열에 있는 모든 수의 제곱을 계산하는 작업</figcaption></figure>



<p class="wp-block-paragraph">위와 같은 예를 보면 Job은 IJob 인터페이스를 구현하는 구조체 유형으로 정의되는데</p>



<p class="wp-block-paragraph">이 인터페이스에는 Execute라는 하나의 필수 메서드가 있고 해당 메서드는 Worker Thread 중 하나에서 Job이 실행될 때 호출된다.</p>



<p class="wp-block-paragraph">NativeArray&lt;int&gt; Nums 배열은 <strong>일반적인 C#으로 관리되는 배열이 아니며 Unity API의 관리되지 않는 네이티브 배열</strong>입니다.</p>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">Job에서 관리되는 오브젝트를 사용하는 게 아예 불가능하지는 않지만 안전한 사용을 위해서는 특별한 주의가 필요하기 때문에</p>



<p class="wp-block-paragraph">보통은 Job에서 관리되는 오브젝트에 액세스하지 않도록 해야 합니다</p>



<p class="wp-block-paragraph">또한 <strong>Burst Compiler</strong>된 코드에서는 관리되는 오브젝트가 결코 허용되지 않기 때문에 <strong>Burst Compiler</strong>의 장점을 활용하려면</p>



<p class="wp-block-paragraph"><strong>Unity의 Job System과 Burst Compiler에서 관리되는 오브젝트(Managed Object)의 사용을 제한해야합니다.</strong></p>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="888" height="593" src="https://lycos7560.com/wp-content/uploads/2025/01/image-7.png" alt="" class="wp-image-39226" style="width:721px;height:auto" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-7.png 888w, https://lycos7560.com/wp-content/uploads/2025/01/image-7-300x200.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-7-768x513.png 768w" sizes="(max-width: 888px) 100vw, 888px" /><figcaption class="wp-element-caption">Monobehaviour(MainThread) Update() 함수 중간의 코드라고 가정</figcaption></figure>



<p class="wp-block-paragraph">먼저 Job의 인스턴스를 생성하고 Nums 필드를 제곱하려는 숫자 배열로 설정한 다음 </p>



<p class="wp-block-paragraph">Job 인스턴스를 글로벌 Job 대기열에 넣는 <strong>Schedule이라는 확장 메서드를 호출</strong>합니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">그리고 일정 시점 후에 대기 상태의 <strong>Worker Thread</strong>가 더 많은 Job을 찾기 위해 대기열을 확인하고</p>



<p class="wp-block-paragraph"><strong>Schedule </strong>메서드로 대기열에 들어간 해당 Job 인스턴스를 대기열에서 가져와서 실행할 수 있습니다.</p>



<p class="wp-block-paragraph">잡을 예약한 후에 일정 시점에 Main Thread는 Schedule에 의해 반환된 Job handle에서 Complete를 호출해야 합니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">Complete를 호출하면 몇 가지 작업이 수행되는 데</p>



<p class="wp-block-paragraph">만약 Complete를 호출하기 전에 Job 실행이 완료되지 않은 경우 <strong>Complete는 잡이 완료될 때까지 대기</strong>합니다.</p>



<p class="wp-block-paragraph">어떤 경우에도 Complete 메서드는 잡이 실행을 완료할 때까지 반환하지 않습니다.</p>



<p class="wp-block-paragraph"><strong>Complete는 Main Thread와 Job을 동기화</strong>하기 위해 사용되고 Main Thread는 작업 완료가 필요할 때 예약된 잡에서 Complete를 호출합니다</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">Complete가 수행하는 다른 중요한 일은 Job 대기열에서 모든 Job의 기록을 제거하는 것 입니다.</p>



<p class="wp-block-paragraph"><strong>리소스 유출을 막으려면 예약된 모든 잡을 완료</strong>해야 하기 때문입니다</p>



<p class="wp-block-paragraph">추가적으로 주의가 필요한 제한이 있는데 <strong>Job을 예약하고 완료할 수 있는 것은 <strong>오직 Main Thread</strong></strong>이고</p>



<p class="wp-block-paragraph"><strong><strong>Main Thread</strong></strong>가 현재 예약된 Job에서 사용 중인 데이터에 액세스해서는 안 된다는 것입니다(네이티브에 접근하지 말 것)</p>



<p class="wp-block-paragraph"><strong>Main Thread는  </strong>Job이 완료된 이후에 배열에 내용에 접근하는 것이 안전합니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="734" height="383" src="https://lycos7560.com/wp-content/uploads/2025/01/image-8.png" alt="" class="wp-image-39227" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-8.png 734w, https://lycos7560.com/wp-content/uploads/2025/01/image-8-300x157.png 300w" sizes="(max-width: 734px) 100vw, 734px" /><figcaption class="wp-element-caption">동일한 배열을 사용하여 이 잡을 두 번 실행</figcaption></figure>



<p class="wp-block-paragraph">잡을 두 번 인스턴스화해서 두 Job의 Nums 필드를 동일한 네이티브 배열로 설정하고</p>



<p class="wp-block-paragraph">그런 다음 두 잡을 예약하려고 하면 두 번째 Schedule 호출에서 안전성 검사 예외가 발생합니다.</p>



<p class="wp-block-paragraph">두 Job이 동일한 유형의 인스턴스라는 것은 부수적인 사실이며 문제와 관계 없습니다.</p>



<p class="wp-block-paragraph"><strong>문제는 예약하려는 두 번째 잡이 이미 예약된 잡과 똑같은 배열에 액세스</strong>하려고 한다는 겁니다.</p>



<p class="wp-block-paragraph">동일한 데이터에 액세스하는 두 Job을 동시에 실행할 수 있도록 허용하면</p>



<p class="wp-block-paragraph">경쟁 상태가 되기 때문에 이미 예약된 잡에서도 사용 중인 데이터에 새 Job이 액세스하는 경우</p>



<p class="wp-block-paragraph"> Schedule 메서드의 안전성 검사에서 예외 오류가 발생합니다.</p>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">종속성을 이용한 해결</h3>



<p class="wp-block-paragraph">해결법 1.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="660" height="313" src="https://lycos7560.com/wp-content/uploads/2025/01/image-9.png" alt="" class="wp-image-39228" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-9.png 660w, https://lycos7560.com/wp-content/uploads/2025/01/image-9-300x142.png 300w" sizes="(max-width: 660px) 100vw, 660px" /><figcaption class="wp-element-caption">첫 번째 Job이 실행되고 완료된 이후를 두 번째 Job을 실행하도록 보장 (메인 스레드가 낭비될 수 도 있는 단점이 있음)</figcaption></figure>



<p class="wp-block-paragraph">해결법 2.</p>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="796" height="305" src="https://lycos7560.com/wp-content/uploads/2025/01/image-10.png" alt="" class="wp-image-39229" style="width:659px;height:auto" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-10.png 796w, https://lycos7560.com/wp-content/uploads/2025/01/image-10-300x115.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-10-768x294.png 768w" sizes="(max-width: 796px) 100vw, 796px" /><figcaption class="wp-element-caption">첫 번째 Job을 두 번째 Job에 종속</figcaption></figure>



<p class="wp-block-paragraph">일반적으로 Worker Thread는 Job의 모든 종속성이 실행을 완료할 때까지 대기열에서 Job을 가져오지 않고</p>



<p class="wp-block-paragraph">안전성 검사는 Job이 자체 종속성과 충돌하지 않는다는 것을 압니다</p>



<p class="wp-block-paragraph">실질적으로 <strong>Job 종속성을 사용하면 필요할 때 Job 간의 실행 순서를 설정</strong>할 수 있습니다.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="964" height="322" src="https://lycos7560.com/wp-content/uploads/2025/01/image-11.png" alt="" class="wp-image-39230" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-11.png 964w, https://lycos7560.com/wp-content/uploads/2025/01/image-11-300x100.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-11-768x257.png 768w" sizes="(max-width: 964px) 100vw, 964px" /></figure>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="591" height="690" src="https://lycos7560.com/wp-content/uploads/2025/01/image-12.png" alt="" class="wp-image-39231" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-12.png 591w, https://lycos7560.com/wp-content/uploads/2025/01/image-12-257x300.png 257w" sizes="(max-width: 591px) 100vw, 591px" /><figcaption class="wp-element-caption">직접적인 종속 관계를 여러 개 갖는 것</figcaption></figure>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="1067" height="731" src="https://lycos7560.com/wp-content/uploads/2025/01/image-13.png" alt="" class="wp-image-39232" style="width:751px;height:auto" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-13.png 1067w, https://lycos7560.com/wp-content/uploads/2025/01/image-13-300x206.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-13-768x526.png 768w" sizes="(max-width: 1067px) 100vw, 1067px" /><figcaption class="wp-element-caption">CombineDependencies을 이용한 논리적 핸들을 결합</figcaption></figure>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="1582" height="694" src="https://lycos7560.com/wp-content/uploads/2025/01/image-14.png" alt="" class="wp-image-39233" style="width:962px;height:auto" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-14.png 1582w, https://lycos7560.com/wp-content/uploads/2025/01/image-14-300x132.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-14-768x337.png 768w, https://lycos7560.com/wp-content/uploads/2025/01/image-14-1536x674.png 1536w" sizes="(max-width: 1582px) 100vw, 1582px" /></figure>



<p class="wp-block-paragraph">특정 Job을 완료하면 그에 종속된 Job도 모두 재귀적으로 완료된다는 것을 알아야 합니다.</p>



<p class="wp-block-paragraph">즉, 이 그래프에서는 <strong>모든 개별 Job의 핸들에 대해 Complete를 호출할 필요는 없고 E, F, I의 핸들만 호출하면 된다는 뜻</strong>입니다.</p>



<p class="wp-block-paragraph">또한 이미 완료된 잡에 대해 Complete를 호출하더라도 문제가 없기 때문에 중복 완료에 대해 걱정할 필요는 없습니다.</p>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="578" height="210" src="https://lycos7560.com/wp-content/uploads/2025/01/image-15.png" alt="" class="wp-image-39234" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-15.png 578w, https://lycos7560.com/wp-content/uploads/2025/01/image-15-300x109.png 300w" sizes="(max-width: 578px) 100vw, 578px" /><figcaption class="wp-element-caption"> 잘못된 순환 종속성</figcaption></figure>



<p class="wp-block-paragraph">순환 Job 종속성을 생성하면 교착 상태가 발생할 수 있기 때문에 생성하지 않는 게 좋습니다.</p>



<p class="wp-block-paragraph">다행히도 그러한 경우는 걱정할 필요가 없습니다.</p>



<p class="wp-block-paragraph">왜냐하면 Job은 자신보다 먼저 예약된 Job에만 종속될 수 있고</p>



<p class="wp-block-paragraph">한 번 예약된 Job은 종속성을 변경할 수 없기 때문입니다.</p>



<p class="wp-block-paragraph">또한 이 두 가지 특성이 함께 작용하기 때문에 순환 종속성 예약은 불가능합니다.</p>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Job 병렬 처리</h3>



<figure class="wp-block-image size-full"><img decoding="async" width="689" height="409" src="https://lycos7560.com/wp-content/uploads/2025/01/image-17.png" alt="" class="wp-image-39238" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-17.png 689w, https://lycos7560.com/wp-content/uploads/2025/01/image-17-300x178.png 300w" sizes="(max-width: 689px) 100vw, 689px" /><figcaption class="wp-element-caption">IJobParallelFor를 이용한 병렬처리</figcaption></figure>



<p class="wp-block-paragraph">잡이 처리하는 배열이나 목록의 요소를 <strong>병렬로 처리</strong>할 수 있습니다.</p>



<p class="wp-block-paragraph">예를 들어 SquaresJob에서는 배열의 요소를 서로 동시에 모두 안전하게 제곱하려고 한다면 <strong>여러 개의 다른 잡으로 수동 분할</strong>할 수도 있습니다.</p>



<p class="wp-block-paragraph">위에 대한 솔루션으로 <strong>IJobParallelFor 인터페이스</strong>를 사용하면 좋습니다</p>



<p class="wp-block-paragraph">이 IJobParallelFor가 실행될 때 Execute 메서드는 배열의 각 인덱스마다 한 번씩 호출되는데 </p>



<p class="wp-block-paragraph"><strong>각 호출은 하나의 요소만 즉 int 파라미터로 전달된 인덱스의 요소만 처리</strong>합니다.</p>



<p class="wp-block-paragraph">배열의 <strong>인덱스에 다른 값이 전달되면 안전성 검사 예외 오류가 발생</strong>합니다.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="1532" height="416" src="https://lycos7560.com/wp-content/uploads/2025/01/image-18.png" alt="" class="wp-image-39239" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-18.png 1532w, https://lycos7560.com/wp-content/uploads/2025/01/image-18-300x81.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-18-768x209.png 768w" sizes="(max-width: 1532px) 100vw, 1532px" /><figcaption class="wp-element-caption">IJobParallelFor를 이용한 병렬처리 작업 예시</figcaption></figure>



<p class="wp-block-paragraph">IJobParallelFor를 예약할 때는 <strong>배열 길이</strong>와 <strong>배치 크기</strong>를 전달합니다.</p>



<p class="wp-block-paragraph">예를 들어 위의 예시의 배열 길이가 250이고 배치 크기가 100인 경우 Job은 3개의 배치로 나누어집니다.</p>



<p class="wp-block-paragraph">첫 번째는 인덱스 0부터 99까지 처리하고</p>



<p class="wp-block-paragraph">두 번째 배치는 인덱스 100부터 199까지 처리하고</p>



<p class="wp-block-paragraph">마지막 배치는 나머지 인덱스 200부터 249까지 처리합니다.</p>



<p class="wp-block-paragraph">Job이 3개의 배치로 나뉘었기 때문에 최대 3개의 <strong>Worker Thread</strong>를 통해 효과적으로 처리됩니다.</p>



<p class="wp-block-paragraph">배치 크기는 각 프로젝트에서 실험을 통해 각 특정 Job에 대한 최적의 크기를 찾아야 합니다.</p>



<p class="wp-block-paragraph">주의할 점은 작은 배치가 너무 많아지면 Job 시스템에 상당한 오버헤드가 발생할 수 있다는 점에 유의해야 합니다.</p>



<p class="wp-block-paragraph">가장 중요한 오버헤드로 <strong>메모리 액세스</strong> 문제가 있습니다.</p>



<p class="wp-block-paragraph">만약 워크로드를 8개 코어로 분산시키면 8개의 코어는 모두 <strong>메모리 액세스를 위해 서로 경쟁해야 하기 때문에 문제가 발생</strong>합니다.</p>



<p class="wp-block-paragraph">8개의 코어가 배열을 읽고 쓰는 속도가 1개의 코어보다 더 빠를 수 없다는 것이 메모리 액세스 오버헤드를 피하는 포인트 입니다.</p>



<p class="wp-block-paragraph">따라서 작업이 가벼운 경우에는 단일 스레드 잡을 사용하는 것이 더 좋습니다.</p>



<p class="wp-block-paragraph">반대로 <strong>메모리 액세스의 바이트당 계산이 더 많이 필요한 경우</strong>에는 작업의 병렬화를 통해 성능을 크게 향상시킬 수 있습니다.</p>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">ECS Entity와 Component</h2>



<p class="wp-embed-aspect-16-9 wp-has-aspect-ratio wp-block-paragraph"><a href="https://www.youtube.com/watch?v=jzCEzNoztzM" target="_blank" rel="noreferrer noopener">https://www.youtube.com/watch?v=jzCEzNoztzM</a></p>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Job과 Unity GameObject의 호환성 문제</h3>



<figure class="wp-block-image size-full"><img decoding="async" width="731" height="308" src="https://lycos7560.com/wp-content/uploads/2025/01/image-19.png" alt="" class="wp-image-39240" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-19.png 731w, https://lycos7560.com/wp-content/uploads/2025/01/image-19-300x126.png 300w" sizes="(max-width: 731px) 100vw, 731px" /><figcaption class="wp-element-caption">Unity의 GameObject</figcaption></figure>



<p class="wp-block-paragraph">게임 오브젝트는 <strong>클래스 유형</strong>이고 <strong>컴포넌트를 위한 컨테이너</strong>입니다.</p>



<p class="wp-block-paragraph">일반적으로 모든 게임 오브젝트는 항상 Transform 컴포넌트를 가지고 있고</p>



<p class="wp-block-paragraph">계층적으로 다른 게임 오브젝트의 부모로 지정될 수 있습니다.</p>



<p class="wp-block-paragraph">게임 오브젝트 컴포넌트는 그 자체로 클래스 유형이고 다른 관리되는 오브젝트에 대한 레퍼런스를 포함할 수도 있습니다.</p>



<p class="wp-block-paragraph">Monobehaviour의 Update()와 같은 컴포넌트의 특정 메서드는 Unity 게임 루프의 특정 지점에서 호출됩니다.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="506" height="467" src="https://lycos7560.com/wp-content/uploads/2025/01/image-20.png" alt="" class="wp-image-39241" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-20.png 506w, https://lycos7560.com/wp-content/uploads/2025/01/image-20-300x277.png 300w" sizes="(max-width: 506px) 100vw, 506px" /><figcaption class="wp-element-caption">Job을 이용하기 위한 Unity의 GameObject에 대한 주요 문제점</figcaption></figure>



<p class="wp-block-paragraph">주요 성능 문제는 게임 오브젝트와 컴포넌트가 <strong>관리되는 오브젝트</strong>이기 때문에</p>



<p class="wp-block-paragraph">버스트 컴파일된 코드에서 사용할 수 없고 특별히 가공한 Job에서만 사용할 수 있다는 점입니다</p>



<p class="wp-block-paragraph"><strong>관리되는 오브젝트는 가비지 컬렉션 오버헤드를 유발하고 생성과 파괴 비용도 그다지 저렴하지 않다는 점</strong>도 이상적할 수 없습니다.</p>



<p class="wp-block-paragraph">마지막으로, 관리되는 다른 오브젝트처럼 <strong>게임 오브젝트와 컴포넌트는 메모리 주변에 분산</strong>되어 있습니다</p>



<p class="wp-block-paragraph">배열에 깔끔하게 패킹되어 있지 않으며 분산된 오브젝트를 순회하는 것은 <strong>비효율적</strong>인 경향이 있습니다.</p>



<p class="wp-block-paragraph"><strong>캐시 부적중이 발생</strong>할 수 있고 시스템 자원의 비용이 들기 때문입니다.</p>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Entity Component System</h3>



<p class="wp-block-paragraph">위의 상황을 방지하기 위해 게임 오브젝트 대신 <strong>Entity</strong>를 사용할 수 있습니다.</p>



<p class="wp-block-paragraph"><strong>Entity는 ID 번호</strong>이며 <strong>ID는 여러 컴포넌트와 연결될 수 있습니다</strong>.</p>



<p class="wp-block-paragraph">유일한 제한은 <strong>단일Entity가 특정 유형의 여러 컴포넌트를 가질 수 없다는 것</strong>입니다.</p>



<p class="wp-block-paragraph">가령 컴포넌트 유형 Foo가 주어진 경우 단일 Entity는 Foo 컴포넌트 하나만 가질 수 있습니다.</p>



<p class="wp-block-paragraph">게임 오브젝트와의 또 다른 차이점은 Entity에는 <strong>부모 지정 개념이 빌트인되어 있지 않다는 것</strong>입니다.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="844" height="285" src="https://lycos7560.com/wp-content/uploads/2025/01/image-21.png" alt="" class="wp-image-39242" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-21.png 844w, https://lycos7560.com/wp-content/uploads/2025/01/image-21-300x101.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-21-768x259.png 768w" sizes="(max-width: 844px) 100vw, 844px" /><figcaption class="wp-element-caption">IComponentData 인터페이스</figcaption></figure>



<p class="wp-block-paragraph">게임 오브젝트의 컴포넌트와 달리 <strong>Entity의 컴포넌트는 일반적으로 IComponentData 인터페이스를 구현한 구조체로 정의되는 관리되지 않는 오브젝트</strong>입니다.</p>



<p class="wp-block-paragraph">이 <strong>IComponentData</strong> 인터페이스에는 메서드가 없지만<strong> 효과적으로 구조체를 컴포넌트 유형으로 표시</strong>합니다.</p>



<p class="wp-block-paragraph">IComponentData 구조체에는 int, float나 Entity ID 형식의 레퍼런스 등 관리되지 않는 데이터 유형만 포함될 수 있습니다.</p>



<p class="wp-block-paragraph">실제로 클래스에 IComponentData를 적용하여 관리되는 컴포넌트 유형을 정의할 수 있습니다.</p>



<p class="wp-block-paragraph">그런 컴포넌트 유형은 다른 관리되는 오브젝트를 포함할 수 있습니다.</p>



<p class="wp-block-paragraph">하지만 관리되는 컴포넌트 유형은 게임 오브젝트의 모든 효율성 문제를 안고 있기 때문에 <strong>반드시 필요할 때만 사용</strong>해야 합니다</p>



<p class="wp-block-paragraph">관리 여부에 상관없이 Entity의 컴포넌트는 일반적으로 순수한 데이터이기 때문에</p>



<p class="wp-block-paragraph">보통 Entity의 컴포넌트에는 메서드가 주어지지 않습니다. (금지는 아님)</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">마지막으로, 나중에 설명하겠지만 Entity와 컴포넌트는 긴밀하게 패킹된 배열에 저장되기 때문에</p>



<p class="wp-block-paragraph"><strong>대량으로 쿼리를 호출해서 액세스하는 것이 효율적</strong>입니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="641" height="224" src="https://lycos7560.com/wp-content/uploads/2025/01/image-22.png" alt="" class="wp-image-39243" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-22.png 641w, https://lycos7560.com/wp-content/uploads/2025/01/image-22-300x105.png 300w" sizes="(max-width: 641px) 100vw, 641px" /><figcaption class="wp-element-caption">IComponent Data를 구현하는 Movement 구조체</figcaption></figure>



<p class="wp-block-paragraph">컴포넌트 유형으로 IComponent Data를 구현하는 Movement 구조체를 예시를 살펴보면</p>



<p class="wp-block-paragraph">구조체에는 2개의 관리되지 않는 필드인 float3 Direction과 float Speed가 있습니다.</p>



<p class="wp-block-paragraph">참고로 float3는 Mathematics 패키지에 정의되어 있습니다</p>



<p class="wp-block-paragraph">vector3처럼 float3는 3개의 플로트를 x, y, z에 저장합니다.</p>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">World와 EntityManager</h3>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="782" height="527" src="https://lycos7560.com/wp-content/uploads/2025/01/image-23.png" alt="" class="wp-image-39244" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-23.png 782w, https://lycos7560.com/wp-content/uploads/2025/01/image-23-300x202.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-23-768x518.png 768w" sizes="(max-width: 782px) 100vw, 782px" /></figure>



<p class="wp-block-paragraph">Entity를 생성하려면 먼저<strong> Entity의 컨테이너인 Wolrd가 필요</strong>합니다.</p>



<p class="wp-block-paragraph">하나의 Wolrd 안에 있는 모든 Entity는 해당 Wolrd 내에서 고유한 ID를 갖지만 (서로 다른 배열의 인덱스처럼)</p>



<p class="wp-block-paragraph">다른 Wolrd 의 엔티티가 동일한 ID를 가질 수 있으므로 잘못된 Wolrd 에서 ID로 엔티티를 찾지 않도록 주의해야 합니다.</p>



<p class="wp-block-paragraph">대부분의 경우 하나의 Wolrd 만 필요하겠지만 논리적 분리가 필요한 경우에는 추가 Wolrd 를 생성하는 것이 유용할 수 있습니다.</p>



<p class="wp-block-paragraph">예를 들어 DOTS Netcode 패키지는 <strong>서버와 각 클라이언트에 대해 별도의 Wolrd를 생성</strong>합니다.</p>



<p class="wp-block-paragraph">Wolrd 의 EntityManager는 <strong>Entity를 생성, 파괴</strong>하고<strong> 컴포넌트를 추가, 제거하고 컴포넌트 값을 받고 설정하는 메서드를 제공</strong>합니다.</p>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="1429" height="160" src="https://lycos7560.com/wp-content/uploads/2025/01/image-24.png" alt="" class="wp-image-39245" style="width:804px;height:auto" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-24.png 1429w, https://lycos7560.com/wp-content/uploads/2025/01/image-24-300x34.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-24-768x86.png 768w" sizes="(max-width: 1429px) 100vw, 1429px" /><figcaption class="wp-element-caption">Archetype과 Chunk</figcaption></figure>



<p class="wp-block-paragraph">Wolrd의 Entity는 Archetype으로 나뉘는데 이때 각 Archetype은 <strong>모든 엔티티를 컴포넌트 유형의 특정 조합으로 저장</strong>합니다.</p>



<p class="wp-block-paragraph">각 Archetype 내에서 Entity와 컴포넌트는 Chunk라고 하는 균일한 크기의 메모리 블록에 저장됩니다.</p>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="1384" height="565" src="https://lycos7560.com/wp-content/uploads/2025/01/image-25.png" alt="" class="wp-image-39246" style="width:884px;height:auto" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-25.png 1384w, https://lycos7560.com/wp-content/uploads/2025/01/image-25-300x122.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-25-768x314.png 768w" sizes="(max-width: 1384px) 100vw, 1384px" /></figure>



<p class="wp-block-paragraph">예를 들어 여기 3개의 Archetype이 있는 단일 World가 있습니다.</p>



<p class="wp-block-paragraph">왼쪽의 Archetyp은 4개의 Chunk를 가지고 있고 컴포넌트 유형이 A, B, C인 모든 Entity를 저장합니다.</p>



<p class="wp-block-paragraph">가운데 Archetype은 2개의 Chunk를 가지고 있고 컴포넌트 유형이 A, B인 모든 Entity를 저장합니다.</p>



<p class="wp-block-paragraph">오른쪽의 Archetype은 3개의 Chunk 타입에 컴포넌트 유형이 A, C인 모든 Entity를 저장하죠.</p>



<p class="wp-block-paragraph">이 구조의 의미는 Entity에서 컴포넌트를 추가하거나 제거하려면 새로운 Archetype으로 Entity를 옮겨야 한다는 겁니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">예를 들어 컴포넌트 <strong>유형이 A, B인 </strong>Entity의 경우<strong> C 유형의 컴포넌트를 추가</strong>하면</p>



<p class="wp-block-paragraph">Archetype이 A와 B인 <strong>현재 Chunk에서 Archetype이 A, B, C인 Chunk로 해당 Entity를 옮겨야 합니다</strong>.</p>



<p class="wp-block-paragraph">다행히도 이 작업을 <strong>수동으로 처리할 필요는 없습니다.</strong></p>



<p class="wp-block-paragraph">사용자로서 <strong>EntityManager 메서드를 호출한 다음 엔티티를 생성, 삭제하고 컴포넌트를 추가, 제거</strong>만 하면 됩니다.</p>



<p class="wp-block-paragraph">EntityManager는 Archetype의 생성과 Chunk 생성, 파괴,  Chunk 간 Entity 이동에 대한 모든 세부 사항을 처리합니다.</p>



<p class="wp-block-paragraph">참고로 Archetype과 Chunk를 수정하는 이러한 작업을 <strong>구조적 변경 작업</strong>이라고 합니다.</p>



<p class="wp-block-paragraph">반면, 저장된 컴포넌트 <strong>값을 단지 읽고 수정하는 것</strong>은 구조적 변경 작업이 아닙니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="1449" height="723" src="https://lycos7560.com/wp-content/uploads/2025/01/image-26.png" alt="" class="wp-image-39247" style="width:1141px;height:auto" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-26.png 1449w, https://lycos7560.com/wp-content/uploads/2025/01/image-26-300x150.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-26-768x383.png 768w" sizes="(max-width: 1449px) 100vw, 1449px" /><figcaption class="wp-element-caption">컴포넌트 유형이 A, B, C인 Archetype의 Chunk 다이어그램</figcaption></figure>



<p class="wp-block-paragraph">Chunk 자체의 구조를 살펴보면 위와 같은 예시로 컴포넌트 유형이 A, B, C인 Archetype의 Chunk 다이어그램이 있다고 한다면</p>



<p class="wp-block-paragraph">각 청크에 들어갈 수 있는 Entity의 수는 컴포넌트 유형의 수와 크기에 따라 달라지지만 <strong>Chunk당 Entity 수는 항상 128개로 제한</strong>됩니다.</p>



<p class="wp-block-paragraph">이 예에서는 Chunk가 128개의 Entity를 수용할 수 있지만 현재는 3개의 엔티티만 저장하고 있다고 가정해 봅시다.</p>



<p class="wp-block-paragraph">이 Chunk의 Archetype에는 세 가지의 컴포넌트 유형이 있으므로 이 Chunk는 총 4개의 배열로 구성됩니다.</p>



<p class="wp-block-paragraph">각 컴포넌트 유형마다 하나씩 그리고 엔티티 ID용 배열 하나 (ID, A, B, C)</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">첫 번째 Entity와 컴포넌트는 이 배열의 인덱스 0에 저장되고</p>



<p class="wp-block-paragraph">두 번째는 인덱스 1에, 세 번째는 인덱스 2에 저장됩니다.</p>



<p class="wp-block-paragraph">일반적으로 Chunk에 저장된 엔티티는 항상 배열의 시작 부분부터 긴밀하게 패킹됩니다.</p>



<p class="wp-block-paragraph">이 상태를 유지하기 위해 Chunk에 추가된 Entity는 항상 첫 번째 빈 슬롯에 배치되고 Entity가 제거되면 마지막 Entity가 빈 공간을 채우기 위해 옮겨집니다.</p>



<p class="wp-block-paragraph">예를 들어 첫 번째 슬롯의 Entity를 제거하면 마지막 Entity가 첫 번째 슬롯을 채우기 위해 옮겨집니다.</p>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="1323" height="1103" src="https://lycos7560.com/wp-content/uploads/2025/01/image-27.png" alt="" class="wp-image-39248" style="width:847px;height:auto" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-27.png 1323w, https://lycos7560.com/wp-content/uploads/2025/01/image-27-300x250.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-27-768x640.png 768w" sizes="(max-width: 1323px) 100vw, 1323px" /><figcaption class="wp-element-caption">인덱스 0 Entity의 제거의 따른 변화 </figcaption></figure>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">추가적으로 컴포넌트 유형은 모든 크기가 될 수 있기 때문에 </p>



<p class="wp-block-paragraph">Chunk의 컴포넌트 배열은 항상 동일한 수의 슬롯을 갖지만 각각 Chunk의 크고 작은 부분을 차지할 수 있습니다. </p>



<figure class="wp-block-image size-full"><img decoding="async" width="1540" height="395" src="https://lycos7560.com/wp-content/uploads/2025/01/image-28.png" alt="" class="wp-image-39249" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-28.png 1540w, https://lycos7560.com/wp-content/uploads/2025/01/image-28-300x77.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-28-768x197.png 768w, https://lycos7560.com/wp-content/uploads/2025/01/image-28-1536x394.png 1536w" sizes="(max-width: 1540px) 100vw, 1540px" /><figcaption class="wp-element-caption">Chunk의 컴포넌트 배열 크기</figcaption></figure>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="1715" height="274" src="https://lycos7560.com/wp-content/uploads/2025/01/image-29.png" alt="" class="wp-image-39250" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-29.png 1715w, https://lycos7560.com/wp-content/uploads/2025/01/image-29-300x48.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-29-768x123.png 768w, https://lycos7560.com/wp-content/uploads/2025/01/image-29-1536x245.png 1536w" sizes="(max-width: 1715px) 100vw, 1715px" /><figcaption class="wp-element-caption">Entity 메타데이터의 배열</figcaption></figure>



<p class="wp-block-paragraph">ID로 Entity를 찾을 수 있게 하려면 <strong>World의 EntityManager가 Entity 메타데이터의 배열</strong>을 유지해야 합니다.</p>



<p class="wp-block-paragraph">각 Entity ID에는 이 메타데이터 배열의 슬롯을 나타내는 인덱스 값이 있고 </p>



<p class="wp-block-paragraph">해당 슬롯에는 해당 Entity가 저장된 Chunk를 가리키는 포인터와 해당 Chunk 안에 있는 Entity의 인덱스가 저장됩니다.</p>



<p class="wp-block-paragraph">특정 인덱스에 해당하는  Entity가 없는 경우 해당 인덱스의 Chunk 포인터는 null입니다.</p>



<p class="wp-block-paragraph">Entity가 파괴된 후 Entity 인덱스를 재사용할 수 있도록 각 <strong>Entity ID에는 버전 번호</strong>도 포함됩니다.</p>



<p class="wp-block-paragraph">예를 들어 인덱스가 1, 2, 5인 Entity가 현재 존재하지 않기 때문에 해당 슬롯의 Chunk 포인터는 모두 null입니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="1230" height="170" src="https://lycos7560.com/wp-content/uploads/2025/01/image-30.png" alt="" class="wp-image-39251" style="width:885px;height:auto" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-30.png 1230w, https://lycos7560.com/wp-content/uploads/2025/01/image-30-300x41.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-30-768x106.png 768w" sizes="(max-width: 1230px) 100vw, 1230px" /><figcaption class="wp-element-caption">Entity의 파괴와 버전 번호</figcaption></figure>



<p class="wp-block-paragraph">Entity가 파괴되면 해당 인덱스에 저장된 버전 번호가 증가합니다.</p>



<p class="wp-block-paragraph">따라서 ID의 버전 번호가 현재 저장된 번호와 <strong>일치하지 않는 경우</strong> 해당 ID는 <strong>이미 파괴된 Entity</strong>를 참조하거나 <strong>존재하지 않는 Entity</strong>를 참조해야 합니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="1469" height="796" src="https://lycos7560.com/wp-content/uploads/2025/01/image-31.png" alt="" class="wp-image-39252" style="width:910px;height:auto" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-31.png 1469w, https://lycos7560.com/wp-content/uploads/2025/01/image-31-300x163.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-31-768x416.png 768w" sizes="(max-width: 1469px) 100vw, 1469px" /><figcaption class="wp-element-caption">Entity Chunk 쿼리</figcaption></figure>



<p class="wp-block-paragraph">Entity 쿼리를 사용하면 지정된 컴포넌트 유형 집합을 가진 모든 Chunk 목록을 <strong>효율적으로 검색</strong>할 수 있습니다.</p>



<p class="wp-block-paragraph">예를 들어 컴포넌트 유형이 A, C인 모든 Chunk를 찾는 쿼리는 </p>



<p class="wp-block-paragraph">왼쪽과 오른쪽 Archetype의 모든 Chunk와 일치하지만 컴포넌트 유형 C가 없으니 가운데 Chunk는 일치하지 않습니다. </p>



<p class="wp-block-paragraph">일부 경우에는 특정 컴포넌트 유형을 가진 Chunk를 필터링하는 쿼리를 사용할 수 있습니다.</p>



<p class="wp-block-paragraph">예를 들어 컴포넌트 A, C를 포함하고 B는 포함하지 않는 쿼리는 오른쪽 Archetype의 Chunk만 일치합니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="975" height="145" src="https://lycos7560.com/wp-content/uploads/2025/01/image-32.png" alt="" class="wp-image-39253" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-32.png 975w, https://lycos7560.com/wp-content/uploads/2025/01/image-32-300x45.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-32-768x114.png 768w" sizes="(max-width: 975px) 100vw, 975px" /><figcaption class="wp-element-caption"><strong>IJobChunk / IJobEntity</strong></figcaption></figure>



<p class="wp-block-paragraph">Job에서 Entity와 컴포넌트에 액세스하려면 두 가지 특수 Job 유형인<strong> IJobChunk</strong>와<strong> IJobEntity</strong>를 사용할 수 있습니다.</p>



<p class="wp-block-paragraph">IJobChunk의 경우 <strong>schedule 메서드에 쿼리를 전달하고 쿼리가 실행될 때 쿼리와 일치하는 모든 Chunk에서 Job이 반복</strong>된 다음</p>



<p class="wp-block-paragraph"><strong>해당 Job이 각 Chunk에서 개별 Entity와 컴포넌트에 액세스</strong>할 수 있습니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">IJobEntity도 동일한 방식으로 작동하지만 <strong>Chunk보다는 개별 Entity에서 직접 반복 작업을 수행</strong>합니다.</p>



<p class="wp-block-paragraph">일반적으로 <strong>IJobChunk는 조금 불편할 수 있지만 명시적으로 낮은 수준의 컨트롤을 제공</strong>합니다.</p>



<p class="wp-block-paragraph">IJobEntity는 일부 <strong>보일러 플레이트</strong> 코드를 작성하기 위해 소스 생성에 의존하기 때문에 더 간단하고 더 편리하지만<br>(보일러플레이트&nbsp;&#8211; 여러 가지 상황에서 거의 또는 전혀 변경하지 않고 재사용)</p>



<p class="wp-block-paragraph">IJobEntity가 일부 특정한 사용 사례를 지원하지 않기 때문에 때로는 IJobChunk를 사용해야 할 수 있습니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="1016" height="217" src="https://lycos7560.com/wp-content/uploads/2025/01/image-33.png" alt="" class="wp-image-39254" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-33.png 1016w, https://lycos7560.com/wp-content/uploads/2025/01/image-33-300x64.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-33-768x164.png 768w" sizes="(max-width: 1016px) 100vw, 1016px" /></figure>



<p class="wp-block-paragraph">마지막으로 Entity는 Unity 씬에 직접 포함될 수 없지만 <strong>Baking이라는 빌드 시간 프로세스가 하위 씬의 게임 오브젝트를 Entity로 전환</strong>합니다.</p>



<p class="wp-block-paragraph">간단히 말하면 <strong>Baking</strong>은 하위 씬의 각 게임 오브젝트마다 하나의 Entity를 생성하고 </p>



<p class="wp-block-paragraph">각 게임 오브젝트의 각 컴포넌트는 Entity의 컴포넌트 값이 추가되고 설정되는 Baker 클래스에 의해 처리됩니다.</p>



<p class="wp-block-paragraph">이렇게 생성된 Entity 집합은 전체를 포함하는 씬 자체가 로드될 때 런타임 시 로드되는 Entity 씬 파일로 직렬화됩니다.</p>



<p class="wp-block-paragraph">Bake된 Entity는 직렬화되기 전에 베이킹 시스템에서 추가로 처리될 수 있기 때문에 보다 고급적인 목적에 유용합니다.</p>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="1722" height="1056" src="https://lycos7560.com/wp-content/uploads/2025/01/image-97.png" alt="" class="wp-image-39355" style="width:891px;height:auto" srcset="https://lycos7560.com/wp-content/uploads/2025/01/image-97.png 1722w, https://lycos7560.com/wp-content/uploads/2025/01/image-97-300x184.png 300w, https://lycos7560.com/wp-content/uploads/2025/01/image-97-768x471.png 768w, https://lycos7560.com/wp-content/uploads/2025/01/image-97-1536x942.png 1536w" sizes="(max-width: 1722px) 100vw, 1722px" /></figure>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="[ECS/DOTS #1] Unity ECS - Entity Component System 베이직 튜토리얼 [Best Tips &amp; Tricks by Unity Japan]" width="1778" height="1000" src="https://www.youtube.com/embed/3m2GI_GSt5Y?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="[ECS/DOTS #2] Unity ECS로 속도 향상, 캐릭터 5000개 만들어 보기 [Best Tips &amp; Tricks by Unity Japan]" width="1778" height="1000" src="https://www.youtube.com/embed/LVjb_fQs2J8?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<p class="wp-block-paragraph">참고 글</p>



<figure class="wp-block-embed is-type-wp-embed"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="tqs8tarc7C"><a href="https://lycos7560.com/unity/unity-manual-job-system-2023-3-%eb%b2%88%ec%97%ad/37818/">Unity Manual / Job system (2023.3) 번역</a></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;Unity Manual / Job system (2023.3) 번역&#8221; &#8212; 어제와 내일의 나 그 사이의 이야기" src="https://lycos7560.com/unity/unity-manual-job-system-2023-3-%eb%b2%88%ec%97%ad/37818/embed/#?secret=cMOyfsDieE#?secret=tqs8tarc7C" data-secret="tqs8tarc7C" width="600" height="338" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p class="wp-block-paragraph"></p>
<p>The post <a href="https://lycos7560.com/unity/dots-%ec%a0%95%eb%a6%ac-1/39221/">DOTS 정리  (1) &#8211; 개념 및 이론</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/unity/dots-%ec%a0%95%eb%a6%ac-1/39221/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
	</channel>
</rss>
