From 5db096060e46540086b44eb54b9ad8f253c4620e Mon Sep 17 00:00:00 2001
From: art <artbiit@naver.com>
Date: Sat, 17 May 2025 16:50:41 +0900
Subject: [PATCH] =?UTF-8?q?[ADD]=20CSV=20Loader=20=EC=B4=88=EC=95=88=20?=
 =?UTF-8?q?=EC=99=84=EB=A3=8C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 Assets/1_Script/System/CSVLoader.cs      | 118 +++++++++++++++++++++++
 Assets/1_Script/System/CSVLoader.cs.meta |  11 +++
 2 files changed, 129 insertions(+)
 create mode 100644 Assets/1_Script/System/CSVLoader.cs
 create mode 100644 Assets/1_Script/System/CSVLoader.cs.meta

diff --git a/Assets/1_Script/System/CSVLoader.cs b/Assets/1_Script/System/CSVLoader.cs
new file mode 100644
index 0000000..5ee3138
--- /dev/null
+++ b/Assets/1_Script/System/CSVLoader.cs
@@ -0,0 +1,118 @@
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using UnityEngine;
+using UnityEngine.Networking;
+/// <summary>
+/// CSV 데이터를 다양한 소스에서 Pull 방식으로 로드할 수 있는 클래스입니다.
+/// 호출 측에서 MoveNext()를 통해 처리 타이밍을 제어하세요.
+/// </summary>
+public static class CSVLoader
+{
+    /// <summary>CSV 파싱 로직 정의</summary>
+    public interface IParser<T>
+    {
+        T Parse(string[] columns);
+    }
+
+    /// <summary>로딩 진행 상태 메타데이터</summary>
+    public struct Progress
+    {
+        public int TotalRows;
+        public int RowsLoaded;
+    }
+
+    /// <summary>URL(HTTP)로부터 다운로드하여 파싱</summary>
+    public static IEnumerator<Progress> Download<T>(string url,
+        IParser<T> parser,
+        List<T> outList)
+    {
+        using var uwr = UnityWebRequest.Get(url);
+        var operation = uwr.SendWebRequest();
+        // 요청이 완료될 때까지 대기 (Progress 타입 반환은 요청 상태 표시용)
+        while (!operation.isDone)
+        {
+            yield return new Progress { TotalRows = 0, RowsLoaded = 0 };
+        }
+
+        if (uwr.result != UnityWebRequest.Result.Success)
+        {
+            Logger.LogError($"CSVLoader: Download failed '{url}': {uwr.error}");
+            yield break;
+        }
+
+        string text = uwr.downloadHandler.text;
+        foreach (var prog in ProcessText(text, parser, outList))
+            yield return prog;
+    }
+
+    /// <summary>절대 경로의 파일을 읽어 파싱</summary>
+    public static IEnumerator<Progress> LoadFromFile<T>(string filePath,
+        IParser<T> parser,
+        List<T> outList)
+    {
+        if (!File.Exists(filePath))
+        {
+            Logger.LogError($"CSVLoader: File not found '{filePath}'");
+            yield break;
+        }
+        string text = File.ReadAllText(filePath);
+        foreach (var prog in ProcessText(text, parser, outList))
+            yield return prog;
+    }
+
+    /// <summary>StreamingAssets 내 상대 경로 또는 URL로부터 로드</summary>
+    public static IEnumerator<Progress> LoadFromStreamingAssets<T>(string relativePath,
+        IParser<T> parser,
+        List<T> outList)
+    {
+        string fullPath = Path.Combine(Application.streamingAssetsPath, relativePath);
+        if (fullPath.StartsWith("http", StringComparison.OrdinalIgnoreCase))
+        {
+            var downloadEnum = Download(fullPath, parser, outList);
+            while (downloadEnum.MoveNext())
+                yield return downloadEnum.Current;
+        }
+        else
+        {
+            var fileEnum = LoadFromFile(fullPath, parser, outList);
+            while (fileEnum.MoveNext())
+                yield return fileEnum.Current;
+        }
+    }
+
+    /// <summary>Resources 폴더 내 TextAsset로부터 로드</summary>
+    public static IEnumerator<Progress> LoadFromResources<T>(string resourcePath,
+        IParser<T> parser,
+        List<T> outList)
+    {
+        var ta = Resources.Load<TextAsset>(resourcePath);
+        if (ta == null)
+        {
+            Logger.LogError($"CSVLoader: Resource not found '{resourcePath}'");
+            yield break;
+        }
+        string text = ta.text;
+        foreach (var prog in ProcessText(text, parser, outList))
+            yield return prog;
+    }
+
+    /// <summary>텍스트를 실제 파싱하여 Progress를 반환하는 내부 모듈</summary>
+    static IEnumerable<Progress> ProcessText<T>(string text,
+        IParser<T> parser,
+        List<T> outList)
+    {
+        var lines = text.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
+        int total = lines.Length;
+        // 첫 번째 줄은 헤더이므로 무시
+        // 두 번째 줄은 설명이므로 무시
+        for (int i = 2; i < total; i++)
+        {
+            var cols = lines[i].TrimEnd('\r').Split(',');
+            outList.Add(parser.Parse(cols));
+            yield return new Progress { TotalRows = total, RowsLoaded = i + 1 };
+        }
+    }
+}
\ No newline at end of file
diff --git a/Assets/1_Script/System/CSVLoader.cs.meta b/Assets/1_Script/System/CSVLoader.cs.meta
new file mode 100644
index 0000000..36638a1
--- /dev/null
+++ b/Assets/1_Script/System/CSVLoader.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1b37f2c717edfd748bf3173c734fee71
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: