Player Input

using UnityEngine;

public class PlayerInput : MonoBehaviour
{
    public float move_x { get; private set; }
    public float move_z { get; private set; }
    public bool jump { get; private set; }

    private void Awake()
    {
        InitFields();
    }

    private void InitFields()
    {
        move_x = 0;
        move_z = 0;
        jump = false;
    }

    private void Update()
    {
        // if (GameManager.game_over) { InitFields(); return; }

        move_x = Input.GetAxisRaw("Horizontal");
        move_z = Input.GetAxisRaw("Vertical");
        jump = Input.GetKeyDown(KeyCode.Space);
    }
}

Player Action

using UnityEngine;

public class PlayerAction : MonoBehaviour
{
    private PlayerInput pi;
    private Rigidbody rb;

    private float move_speed;
    private float jump_force;
    private float vel_damp;

    [SerializeField] private Transform cam_transform;

    private void Awake()
    {
        GetReferences();
        InitFields();
    }

    private void GetReferences()
    {
        pi = GetComponent<PlayerInput>();
        rb = GetComponent<Rigidbody>();
    }

    private void InitFields()
    {
        move_speed = 100f;
        jump_force = 500f;
        vel_damp = 0.75f;
    }

    private void Start()
    {
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
    }

    private void FixedUpdate()
    {
        Move();
        // if (pi.jump) Jump();
    }

    private void Update()
    {
        if (pi.jump && IsGrounded()) Jump();
        transform.rotation = Quaternion.Euler(transform.eulerAngles.x, cam_transform.eulerAngles.y, transform.eulerAngles.x);
    }

    private bool IsGrounded()
    {
        Collider[] hits = Physics.OverlapBox(new Vector3(transform.position.x, transform.position.y - 1f, transform.position.z), new Vector3(0.5f, 0.25f, 0.5f), Quaternion.identity, LayerMask.GetMask("Ground"));
        if (hits.Length > 0) return true;
        return false;
    }

    private void Move()
    {
        Vector3 move_dir = (transform.right * pi.move_x + transform.forward * pi.move_z).normalized;
        rb.linearVelocity += move_dir * move_speed * Time.fixedDeltaTime;
        rb.linearVelocity = new Vector3(rb.linearVelocity.x * vel_damp, rb.linearVelocity.y, rb.linearVelocity.z * vel_damp);
    }

    private void Jump()
    {
        rb.linearVelocity = new Vector3(rb.linearVelocity.x, 0, rb.linearVelocity.z);
        rb.AddForce(Vector3.up * jump_force);
    }
}

Main Camera

  • Cinemachine Brain

Virtual Camera

  • Cinemachine Camera
    Tracking Target : Player
    Position Control : Orbital Follow
    Rotation Control : Rotation Composer
  • Cinemachine Orbital Follow
    Position Damping : X, Y, Z 전부 0 (안 그러면 카메라가 플레이어를 제대로 따라가지 못하고, 중간에 뚝뚝 끊김!)
    Orbit Style : Sphere
    Radius : 10
  • Cinemachine Rotation Composer
  • Cinemachine FreeLook Modifier
  • Cinemachine Input Axis Controller

읽어주셔서 감사합니다!

동작 방식

  1. 먼저 game_version을 정해준다. 버전이 다른 경우 다르게 동작할 가능성이 있기 때문이다.
  2. 이후 변경한 설정을 바탕으로 Master Server와의 연결을 시도한다.
  3. 만일 연결에 성공했다면 다음 단계로 넘어가고, 연결에 실패했다면 연결에 성공할 때까지 계속해서 ConnectUsingSettings()를 호출한다. (여기서 더 나아가서 만일 연결을 시도한지 1분이 넘었다면 개인전 화면으로 넘어가게 만들 수도 있다!)
  4. 이제 join button을 눌렀는가?를 동작 기준으로 삼는다.
  5. Master Server와 연결이 되어있는 상태에서 join button을 눌렀다면 RandomRoom에 Join한다. 
  6. Master Server와 연결이 되어있지 않은 상태에서 join button을 눌렀다면 다시 ConnectUsingSettings()를 호출한다. (참고로 join button을 누를 수 있다는 사실 자체가 Master Server와 연결이 되었다는 것을 보장하긴 하지만, 혹시 몰라서 넣어둔 것이다!)
  7. Room Joining에 성공했다면 Scene을 전환한다. 여기서 Main Scene은 게임이 이루어지는 Scene이라고 생각하면 된다. 
  8. Room Joining에 실패했다면 3인용 Room을 따로 판다. 호스트는 룸을 새로 생성한 클라이언트에게 할당된다.

Lobby Manager 코드

using Photon.Pun; // 고수준 API (90%의 Unity 게임은 이것만 사용해도 상관없음)
using Photon.Realtime; // 저수준 API (세밀한 조작이 필요한 경우 사용)
using UnityEngine;
using UnityEngine.UI;

public class LobbyManager : MonoBehaviourPunCallbacks
{
    [SerializeField] private string game_version;

    [SerializeField] private Text info_text;
    [SerializeField] private Button join_button;

    private void Awake()
    {
        DoInitialSettings();
    }

    private void DoInitialSettings()
    {
        PhotonNetwork.GameVersion = game_version;
    }

    private void Start() 
    {
        TryJoiningMasterServer();
    }

    // try joining the master server
    private void TryJoiningMasterServer()
    {
        PhotonNetwork.ConnectUsingSettings(); // use the settings(for now, it's game_version) to connect to the master server
        join_button.interactable = false;
        info_text.text = "trying to join the master server...";
    }

    // automatically called when the connection to master server succeeds
    public override void OnConnectedToMaster()
    {
        join_button.interactable = true;
        info_text.text = "connected (master server)";
    }

    // automatically called when the connection to master server fails
    public override void OnDisconnected(DisconnectCause cause)
    {
        join_button.interactable = false;
        info_text.text = "failed to connect (master server)\nretrying to connect...";
        PhotonNetwork.ConnectUsingSettings();
    }

    // when pressing the join button (UI)
    public void Connect() 
    {
        join_button.interactable = false;

        if (PhotonNetwork.IsConnected) // if connected to the master server (true)
        {
            info_text.text = "joining room...";
            PhotonNetwork.JoinRandomRoom(); // if success, OnJoinedRoom() || if fails, OnJoinRandomFailed()
        }
        else // if NOT connected to the master server (false)
        {
            info_text.text = "not connected to the master server!\nretrying to connect...";
            PhotonNetwork.ConnectUsingSettings();
        }
    }

    // when there's no VACANT room
    public override void OnJoinRandomFailed(short returnCode, string message) 
    {
        info_text.text = "no vacant room exists.\ncreating a new room...";
        PhotonNetwork.CreateRoom(null, new RoomOptions { MaxPlayers = 3 }); // 룸은 리슨 서버 방식으로 동작함. 룸을 생성한 클라이언트가 호스트로 선정됨.
    }

    // when succeeded to join the room
    public override void OnJoinedRoom() 
    {
        info_text.text = "joined the room!";
        PhotonNetwork.LoadLevel("Main"); // LoadLevel != SceneManager.LoadScene("~~~"); 거의 비슷하지만 Photon 쪽이 동기화를 제공함.
    }
}

참고사항

  • LobbyManager Script는 Monobehaviour 대신 MonoBehaviourPunCallbacks를 상속한다. OnConnectedToMaster와 같은 커스텀 이벤트를 받기 위함이다. 
  • MonoBehaviourPunCallbacks에 있는 커스텀 이벤트를 사용하기 위해선 꼭 override를 해야 한다. 

읽어주셔서 감사합니다!

시작하기 전에...

필요한 배경지식을 간단히 정리해보겠다.


멀티플레이 시스템의 종류

  • 전용 서버(Dedicated Server) :
    클라이언트로부터 "행동 입력"을 받는 서버이다. 예를 들어, 클라이언트 A가 "총을 쐈다"라는 행동 입력을 보내면 서버에서 총알이 누군가를 맞췄는지, 만일 맞췄다면 데미지는 얼마나 넣을 것인지 등을 모두 "판정"하여 그 결과를 다른 클라이언트들에게 보내준다. 만일 클라이언트 측에서 조작된 입력(예: 1초에 100발 쏘기, 순간이동 등)을 보낸다면, 서버는 그 유효성을 판단하여 무시할 수 있다. 서버가 해킹되지 않는 이상 클라이언트 측에서 할 수 있는 조작-행동이 아무것도 없기 때문에, 가장 보안성이 높다. 그러나 비용이 가장 많이 든다. --> 배틀그라운드, 롤
  • 리슨 서버(Listen Server) :
    클라이언트가 서버 역할도 겸하는 연결 방식이다. 이러한 클라이언트를 "방장 또는 호스트"라고 일컫는다. 전용 서버와 같이 호스트가 유효성을 판정한다. 그러나 호스트 역할을 맡은 클라이언트가 조작된 입력을 보내는 것은 막을 수 없다. 또한 호스트가 중간에 게임을 종료한 경우, 새로운 호스트를 선정하는 절차도 필요해진다. 게임사에서 따로 서버를 운영할 필요가 없어서 비용이 들지 않지만 보안성이 꽤 낮다. --> 마인크래프트, 스타크래프트
  • P2P(Peer-to-Peer) :
    모든 클라이언트가 서로와 소통하는 구조이다. "서버"가 없으므로 따로 유효성을 검사하는 절차가 존재하지 않는다. 행동 입력과 결과 판정을 각자의 클라이언트에서 하며, 계속해서 서로의 월드 속 정보를 동기화한다. 매우 쉽게 구현할 수 있다. 그러나 아무 클라이언트나 조작을 가해도 그 조작이 다른 클라이언트에게 유효성 검사를 거치지 않은 채로 동기화되기 때문에, 가장 보안성이 낮은 연결 방식이다. --> 하이퍼 캐주얼 게임

동작 방식 (Dedicated Server)

  1. 전용 서버에서 클라이언트의 룸(서버 인스턴스) 생성 요청을 받는다. 요청이 유효하다면 룸을 생성한다.
  2. 전용 서버가 만든 룸을 True World로 선정한다.
  3. 전용 서버는 연결된 다른 클라이언트로부터 행동 입력을 실시간으로 받는다. (유효성을 검사한다)
  4. 받은 행동 입력은 True World에서만 실행되며, 그 결과를 연결된 다른 클라이언트에게 일방적으로 보낸다.
  5. 결과를 받은 이들은 그에 맞춰 각자의 월드를 구현한다.

동작 방식 (Listen Server)

  1. 전용 서버에서 클라이언트의 룸 생성 요청을 받는다. 요청이 유효하다면 룸을 생성한다.
  2. 호스트를 선정한다.
  3. 호스트는 자기 자신 & 연결된 다른 클라이언트로부터 행동 입력을 실시간으로 받는다. (유효성을 검사한다)
  4. 받은 행동 입력은 호스트의 월드에서만 실행되며, 그 결과를 자기 자신 & 연결된 다른 클라이언트에게 일방적으로 보낸다.
  5. 결과를 받은 이들은 그에 맞춰 각자의 월드를 구현한다.
  6. 참고 : 만일 플레이어가 3명이면, 월드도 3개다. 그 중 호스트의 월드를 True World로 선정하고, 그에 맞춰 동기화를 하는 것이다.

동작 방식 (P2P)

  1. 전용 서버에서 클라이언트의 룸 생성 요청을 받는다. 요청이 유효하다면 룸을 생성한다.
  2. 모든 클라이언트는 룸 안에 있는 다른 모든 클라이언트에게 자신의 행동 입력을 보낸다.
  3. 각각의 클라이언트는 받은 행동 입력을 자신의 월드에서 실행한다.
  4. 정해진 주기에 맞춰 서로의 "현재 월드 상태"를 동기화한다. 

필수적으로 요구되는 서버

위에서 봤듯이 어떤 멀티플레이 시스템을 사용하든지 간에, 매치메이킹을 위한 전용 서버(Dedicated Server)는 반드시 필요하다. 전용 서버에서 모든 것이 이루어지는 게임은 당연히 필요할 것이고, 리슨 서버나 P2P 방식을 이용한다고 하더라도 "게임 시작"을 클라이언트가 요청했을 때 그 요청을 받고 호스트를 지정하거나 IP/포트를 공유할 수 있도록, 지속적으로 대기하고 있는 서버가 필요하기 때문이다.


필요한 Package

Unity Asset Store에서 PUN 2를 검색하면 다운로드 받을 수 있다;

https://assetstore.unity.com/packages/tools/network/pun-2-free-119922

 

PUN 2 - FREE | 네트워크 | Unity Asset Store

Get the PUN 2 - FREE package from Photon Engine and speed up your game development process. Find this & other 네트워크 options on the Unity Asset Store.

assetstore.unity.com


Package 세팅

  1. Window >> Photon Unity Networking >> PUN Wizard를 클릭한다.
  2. 만일 Photon Engine 계정이 있다면, Setup Project를 클릭한다. 로그인 하자마자 20 CCU라고 되어있는 창 밑에 AppID가 보일 것이다. 더블 클릭하여 복사해준 뒤, Unity Editor로 돌아와서 "AppId or Email" 칸에 붙여넣고, 밑에 있는 Setup Project를 눌러주면 된다.
  3. 만일 Photon Engine 계정이 없다 하더라도, Setup Project를 그대로 클릭한다. 그리고 "AppId or Email" 칸에 자신의 email을 친다. 그러면 그걸 통해서 계정을 생성할 수 있는 웹사이트로 이동할 것이다. 계정을 생성한 뒤, 2번 과정을 따라하면 된다. (만일 이게 잘 안 된다면, 그냥 공식 홈페이지에 들어가서 계정을 직접 생성한 뒤 왼쪽 상단의 Your 탭에서 Apps를 클릭하여 직접 AppId를 찾는 게 빠를 수도 있다!)

읽어주셔서 감사합니다!

#1
배경지식 & 패키지 세팅

 

[Unity] 멀티플레이어 게임 만들기 #1 (배경지식 & 패키지 세팅)

시작하기 전에...필요한 배경지식을 간단히 정리해보겠다.멀티플레이 시스템의 종류전용 서버(Dedicated Server) :클라이언트로부터 "행동 입력"을 받는 서버이다. 예를 들어, 클라이언트 A가 "총을

quickclid.tistory.com


#2
로비 구현

 

[Unity] 멀티플레이어 게임 만들기 #2 (로비 구현)

동작 방식먼저 game_version을 정해준다. 버전이 다른 경우 다르게 동작할 가능성이 있기 때문이다.이후 변경한 설정을 바탕으로 Master Server와의 연결을 시도한다.만일 연결에 성공했다면 다음 단계

quickclid.tistory.com


읽어주셔서 감사합니다!

틀린 문제

  • I've never seen a dawn like it! >> 하나의 독특한 경험이므로 'a'를 넣어줘야 한다. 
  • I had a nice lunch at the Ritz >> 마찬가지로 하나의 독특한 lunch를 의미하고 있으므로, 'a'를 넣어줘야 한다.
  • Have you ever worked in a factory? >> 공장에서 일해본 경험이 있냐는 뜻이므로, 'a'를 넣어줘야 한다. factory는 가산 & 단수 명사라서 관사가 무조건 들어가야 하는데, the를 넣으면 청자도 알고 있는 공장이여야 하기 때문에 안 된다.
  • My father went to BLANK sea when he was 14. >> 'went to sea' 자체가 '선원으로써의 삶을 시작하다'라는 숙어이므로, 아무것도 넣으면 안 된다.
  • John's at BLANK work at the moment. >> 'at work' 자체가 숙어다.  

몰랐던 단어

  • I'll do it by Monday >> "월요일 전까지 할게요"라는 뜻이다. until을 넣으면 "월요일까지 할게요"가 되는 것과는 대조적이다.
  • bay >> 만
  • canal >> 운하
  • straits >> 해협
  • splended >> 멋진/화려한/눈부신/빛나는
  • innocuous >> 무해한
  • jockeying >> 교묘하게 다투다, 신경전을 벌이다 (정치권)
  • lately >> 최근에(=nowadays, 시점을 나타내는 경우)

읽어주셔서 감사합니다!

해석하기 어려웠던 문장

  • The children have gone home. >> 아이들이 집에 가버렸으며, 현재까지도 집에 있다. (have + past participle은 어떠한 것/행위/상황이 현재까지도 영향을 미치고 있음을 강조하여 표현하는 방법이다!)
  • The visitors have seen the new buildings. >> 그 방문자들은 새 빌딩들을 본 적이 있다. (have seen은 뭔가를 본 적이 있다고도 해석될 수 있다!)
  • Has the guest enjoyed the meal? >> 손님이 식사를 즐겼나요? (방금 전에 마친 식사에 대해 묻는 것이다. 참고로 Did the guest enjoy the meal?이라고 하면, 한참 전의 식사에 관해 묻는 느낌이 난다!)
  • 1.1.C부터 체크할 것!

몰랐던 단어

  • The shop assistant is wrapping the parcel >> 소포
  • The plumber is going to fix the pipe >> 배관공
  • You didn't pack this suitcase carefully >> 여행 가방 (특히 네모난 케이스에 손잡이가 달린 것을 의미함
  • You'll have to get a loan from the bank >> 대출
  • My term begins in October >> 임기, 학기
  • A sentence can take any one of four forms : a statement, a question, a command, an exclamanation >> 감탄사
  • When we write a sentence, we must begin with a capital letter and end with a full stop. >> 마침표
  • 1.1.C부터 체크할 것!

읽어주셔서 감사합니다!

오늘은...

투혼에서 프로토스로 컴퓨터(저그) 3마리를 잡아볼 것이다.


전략

  • 일단은 클리어만 해보자는 마음으로 "패스트 캐리어" 전략을 사용했다.
  • 입구 막기 -> 더블넥 -> 테크 올리고 33업 -> 캐리어 12대로 청소 -> 청소하면서 캐리어 추가 생산

플레이 영상

https://youtu.be/16FTsLDCmaU

포톤 캐논과 함께라면 히드라 3부대도 두렵지 않다!

소감

심시티의 중요성을 실감했다. 이론상 "적의 공중유닛이 나오기 전까지 마음놓고 발전할 수 있는" 기반이 아닌가? 이런 식으로 한다면 헌터에서의 1대7도 해볼만한 싸움이 될 것 같다. 

 

그렇지만 나도 언젠가는 전면전을 통해 승리를 쟁취해보고 싶다. 물론 일단은 컨트롤을 할 실력이 안 되니, 이런 식으로 전략을 잘 수립함으로써 게임 이해도를 높이는 데만 집중하자. 그 후 APM을 조금씩 늘려보다 보면 언젠가는 공방 양민에 도달할 수 있지 않을까 ㅎㅎ


읽어주셔서 감사합니다!

using UnityEngine;
using System;
using GoogleMobileAds;
using GoogleMobileAds.Api;
using UnityEngine.SceneManagement;

public class AdManager : MonoBehaviour
{
    public static GameObject ad_manager;
    private BannerView banner_view;

    private void Awake()
    {
        if (ad_manager == null)
        {
            ad_manager = gameObject;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    public void Start()
    {
        MobileAds.Initialize((InitializationStatus initStatus) => { }); // Initialize the Google Mobile Ads SDK.
        SceneManager.sceneLoaded += OnSceneLoaded;
    }

    private void RequestBanner()
    {
        #if UNITY_ANDROID
            string _adUnitId = "ca-app-pub-3940256099942544/6300978111";
        #elif UNITY_IPHONE
            string _adUnitId = "ca-app-pub-3940256099942544/2934735716";
        #else
            string _adUnitId = "unused";
        #endif

        //Clean up banner ad before creating a new one.
        if (banner_view != null)
        {
            banner_view.Destroy();
        }

        // Create a adaptively-sized banner at top of the screen
        AdSize adaptiveSize = AdSize.GetCurrentOrientationAnchoredAdaptiveBannerAdSizeWithWidth(AdSize.FullWidth);
        banner_view = new BannerView(_adUnitId, adaptiveSize, AdPosition.Bottom);

        // create our request used to load the ad.
        var adRequest = new AdRequest();

        // send the request to load the ad.
        banner_view.LoadAd(adRequest); //Debug.Log("Loading banner ad.");
    }

    private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    {
        if (scene.name == "AD Scene")
        {
            RequestBanner();
        }
        else
        {
            if (banner_view != null)
            {
                banner_view.Destroy();
            }
        }
    }
}

문제

2단계 cache simulator를 구현하여라. LRU와 FIFO 정책을 지원해야 한다. 명령 인수로 초기 조건(set index bits, block offset bits, associativity, verbosity, trace file)을 받아야 한다. 


필요한 사전 지식

  • Linux Shell 사용법
  • Cache 작동 방식
  • C 언어의 코딩 협약(convention)

대면 소감

사형 선고와도 같았다. 나는 Linux Shell을 쓸 줄도 몰랐고, Cache 작동 방식도 문제만 적당히 풀 수 있을 정도로 어렴풋이 알고 있었으며, C 언어로는 코테 문제만 좀 끄적여본 게 다였다. 그나마 Skeleton Code를 주셔서 다행이었다.


테스트 방식

  • 주어진 trace에 적힌 명령어(예시 : L, 0x10, 1)를 읽어서, 주어진 csim-ref와 같은 결과를 출력하면 된다.
  • 출력해야 할 값으로는 L1_Hit, L1_Miss, L1_Eviction, L2 ~~~, Mem_Write 등이 있다. 

삽질 과정

Windows를 쓰고 있었기에, 어찌저찌 WSL를 깔았다. 그 후, Linux 전용 컴파일 문구와 ls, cd 같은 간단한 명령어를 익혔다. 

테스트를 편하게 하기 위해서 복붙해놓은 명령어들;

 

정답 simulator (csim-ref)
./csim-ref -v -s 2 -E 2 -b 2 -p 0 -t traces/yi.trace

compile & build
gcc -g -o csim.exe csim.c cachelab.c -lm

검사
./csim.exe -v -s 2 -E 2 -b 2 -p 0 -t traces/yi.trace
./csim.exe -v -s 2 -E 2 -b 2 -p 1 -t traces/yi.trace


cachelab.h

cachelab.c를 위한 헤더 파일이다. arrCount로 결과를 출력해야 한다. 헤더 파일이 두 번 이상 포함되지 않도록 #ifndef로 헤더 가드를 만든 점이 인상깊다. 이외에도 verbosity, policy와 같은 핵심 인수들이 선언되어 있다.

/* 
 * cachelab.h - Prototypes for Cache Lab helper functions
 */

#ifndef CACHELAB_TOOLS_H
#define CACHELAB_TOOLS_H

#define MAX_TRANS_FUNCS 100

typedef struct trans_func
{
  void (*func_ptr)(int M,int N,int[N][M],int[M][N]);
  char* description;
  char correct;
  unsigned int num_hits;
  unsigned int num_misses;
  unsigned int num_evictions;
} trans_func_t;

typedef enum Type
{
	L1_Hit = 0,
	L1_Miss,
	L1_Eviction,
	L2_Hit,
	L2_Miss,
	L2_Eviction,
	L2_Write,
	Mem_Write,
	
	TYPECOUNT	
} TYPE;

extern int arrCount[TYPECOUNT];
extern int verbosity; /* print trace if set */
extern int cachePolicy; // 0 : lru, 1 : fifo
/* 
 * printSummary - This function provides a standard way for your cache
 * simulator * to display its final hit and miss statistics
 */ 
void printSummary(); /* number of evictions */

void initCount();

void Verbose(TYPE t);

#endif /* CACHELAB_TOOLS_H */

cachelab.c

printSummary가 있지만, Verbose를 통해 결과를 한눈에 볼 수 있었기에, 딱히 쓰진 않았다(?)

/*
 * cachelab.c - Cache Lab helper functions
 */
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "cachelab.h"
#include <time.h>

int arrCount[TYPECOUNT];
int verbosity; /* print trace if set */
int cachePolicy; // 0 : lru, 1 : fifo

void initCount()
{
	for(int i = 0; i < TYPECOUNT; i++)
	{
		arrCount[i] = 0;
	}
}

void Verbose(TYPE t)
{
	arrCount[t]++;
		
	if(verbosity)
	{
		switch(t)
		{
			case 	L1_Miss:
				printf("L1 Miss ");
				break;
			case	L1_Hit:
				printf("L1 Hit ");
				break;
			case	L1_Eviction:
				printf("L1 Eviction ");
				break;
			case 	L2_Miss:
				printf("L2 Miss ");
				break;
			case	L2_Hit:
				printf("L2 Hit ");
				break;
			case	L2_Eviction:
				printf("L2 Eviction ");
				break;
			case	L2_Write:
				printf("L2 Write ");
				break;
			case	Mem_Write:
				printf("Mem Write ");
				break;
			default:
				break;
		}
	}
	
}

/* 
 * printSummary - Summarize the cache simulation statistics. Student cache simulators
 *                must call this function in order to be properly autograded. 
 */
void printSummary()
{
    FILE* output_fp = fopen("./csim_results", "w");
    assert(output_fp);
      
    for(int i = 0; i < TYPECOUNT; i++)
    {
    	fprintf(output_fp, "%d ", arrCount[i]);
    }
    fprintf(output_fp, "\n");
	fclose(output_fp);
}

csim.c

이런 Skeleton Code를 볼 때는, main 함수부터 읽어야 한다고 들었다. 대략적인 흐름은; 명령인수 읽기, 캐시들의 기본 스펙 계산, 카운터 초기화, trace 파일 읽기 -> accessData(), freeCache()이다. 

매우 화가 나는 점 : getopt.h와 unistd.h를 VSCode/VS에서 둘 다 지원을 안 해준다. 그래서 Linux Shell로 디버깅을 했는데, 고급 IDE(?)들과는 다르게 그냥 Segment Fault를 내고 죽어버리는 꼴을 여러 번 봐야해서 정말 힘들었다.

/* 
 * csim.c - A cache simulator that can replay traces from Valgrind
 *     and output statistics such as number of hits, misses, and
 *     evictions.  The replacement policy is LRU.
 *
 * Implementation and assumptions:
 *  1. Each load/store can cause at most one cache miss. (I examined the trace,
 *  the largest request I saw was for 8 bytes).
 *  2. Instruction loads (I) are ignored, since we are interested in evaluating
 *  trans.c in terms of its data cache performance.
 *  3. data modify (M) is treated as a load followed by a store to the same
 *  address. Hence, an M operation can result in two cache hits, or a miss and a
 *  hit plus an possible eviction.
 *
 * The function printSummary() is given to print output.
 * Please use this function to print the number of hits, misses and evictions.
 * This is crucial for the driver to evaluate your work. 
 */
#include <getopt.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <assert.h>
#include <math.h>
#include <limits.h>
#include <string.h>
#include <errno.h>
#include "cachelab.h"

//#define DEBUG_ON 
#define ADDRESS_LENGTH 64

extern int arrCount[TYPECOUNT];
extern int verbosity; /* print trace if set */
extern int cachePolicy; // 0 : lru, 1 : fifo

/* Type: Memory address */
typedef unsigned long long int mem_addr_t;

/* Type: Cache line
   LRU is a counter used to implement LRU replacement policy  */
typedef struct cache_line {
    char valid;
    mem_addr_t tag;
    unsigned long long int lru;
    char dirty;
} cache_line_t;

typedef cache_line_t* cache_set_t;
typedef cache_set_t* cache_t;

/* Globals set by command line args */
//int verbosity = 0; /* print trace if set */
int s = 0; /* set index bits */
int s2 = 0; /* set index bits for L2 */
int b = 0; /* block offset bits */
int E = 0; /* associativity */
char* trace_file = NULL;

/* Derived from command line args */
int S; /* number of sets */
int B; /* block size (bytes) */
int S2; /* number of sets in L2 */
int E2; /* associativity of L2 */

/* Counters used to record cache statistics */
unsigned long long int lru_counter = 1;
unsigned long long int l2_lru_counter = 1;

int* l1_fifo = 0;
int* l2_fifo = 0;

/* The cache we are simulating */
cache_t cache, cache_l2;  
mem_addr_t set_index_mask, set_index_mask_l2;

/* 
 * initCache - Allocate memory, write 0's for valid and tag and LRU
 * also computes the set_index_mask
 */
void initCache()
{
    int i,j;
    cache = (cache_set_t*) malloc(sizeof(cache_set_t) * S);
    cache_l2 = (cache_set_t*)malloc(sizeof(cache_set_t) * S2);
    for (i=0; i<S; i++){
        cache[i]=(cache_line_t*) malloc(sizeof(cache_line_t) * E);
        for (j=0; j<E; j++){
            cache[i][j].valid = 0;
            cache[i][j].tag = 0;
            cache[i][j].lru = 0;
            cache[i][j].dirty = 0;
        }
    }

    for(i = 0; i < S2; i++){
	    cache_l2[i] = (cache_line_t*) malloc(sizeof(cache_line_t) * E2);
	    for(j = 0; j < E2; j++){
		    cache_l2[i][j].valid = 0;
		    cache_l2[i][j].tag = 0;
		    cache_l2[i][j].lru = 0;
            cache_l2[i][j].dirty = 0;
	    }
    }

    /* Computes set index mask */
    set_index_mask = (mem_addr_t) (pow(2, s) - 1);
    set_index_mask_l2 = (mem_addr_t) (pow(2, s2) - 1);

    l1_fifo = (int*)malloc(sizeof(int) * S);
    l2_fifo = (int*)malloc(sizeof(int) * S2);  
    for (i=0; i<S; i++){  
    	l1_fifo[i] = 0;
    }
    for (i=0; i<S2; i++){  
    	l2_fifo[i] = 0;
    }
}


/* 
 * freeCache - free allocated memory
 */
void freeCache()
{
    int i;
    for (i=0; i<S; i++){
        free(cache[i]);
    }
    free(cache);

    for(i = 0; i < S2; i++){
	    free(cache_l2[i]);
    }
    free(cache_l2);

    free(l1_fifo);
    free(l2_fifo); 
}


/* 
 * accessData - Access data at memory address addr.
 *   If it is already in cache, increast hit_count
 *   If it is not in cache, bring it in cache, increase miss count.
 *   Also increase eviction_count if a line is evicted.
 *   Process writes if write == 1, process reads if write == 0
 */

/*
    Load : write = 0
    Save, Modify : write = 1
*/
void accessData(mem_addr_t addr, int write)
{
    mem_addr_t set_index = (addr >> b) & set_index_mask;
    mem_addr_t tag = addr >> (s + b);
    mem_addr_t set_index_l2 = (addr >> b) & set_index_mask_l2;
    mem_addr_t tag_l2 = addr >> (s2 + b);

    // check L1 hit
    for (int i = 0; i < E; i++)
    {
        if (cache[set_index][i].valid && cache[set_index][i].tag == tag)
        {
            arrCount[L1_Hit]++;
            cache[set_index][i].lru = 0; // Reset LRU
            return; // L1 hit -> return;
        }
    }

    // L1 miss
    arrCount[L1_Miss]++;

    // check L2 hit
    for (int i = 0; i < E2; i++)
    {
        if (cache[set_index_l2][i].valid && cache[set_index_l2][i].tag == tag_l2)
        {
            arrCount[L2_Hit]++;
            cache[set_index_l2][i].lru = 0; // Reset LRU

            // L1 write-back (L1 miss, L2 hit)
            int empty_index = -1; // for L1
            for (int j = 0; j < E; j++)
                if (!cache[set_index][j].valid)
                    empty_index = j;
            
            if (empty_index != -1) // if L1 has vacant space
            {
                cache[set_index][empty_index].valid = 1;
                cache[set_index][empty_index].tag = tag_l2;
                cache[set_index][empty_index].lru = 0;
            }
            else // do L1 eviction
            {
                arrCount[L1_Eviction]++;
                int evicted_index = -1;

                if (cachePolicy == 0) // LRU
                {
                    int max_lru = -1;
                    for (int j = 0; j < E; j++)
                    {
                        if (cache[set_index][i].lru > max_lru)
                        {
                            max_lru = cache[set_index][i].lru;
                            evicted_index = i;
                        }
                    }
                }
                else // FIFO
                {
                    // ...
                }
                printf("%d", empty_index); // =========================================================
                cache[set_index][empty_index].valid = 1;
                cache[set_index][empty_index].tag = tag_l2;
                cache[set_index][empty_index].lru = 0;
            }

            return; // L1 miss, L2 hit -> L1 write-back, return;
        }
    }

    // L2 miss
    arrCount[L2_Miss]++;

    // Load from RAM =============================================================================================================================================
    // L2 write-back
    int empty_index_l2 = -1; // for L2
    for (int i = 0; i < E2; i++)
        if (!cache_l2[set_index_l2][i].valid)
            empty_index_l2 = i;

    if (empty_index_l2 != -1) // if L2 has vacant space
    {
        cache_l2[set_index_l2][empty_index_l2].valid = 1;
        cache_l2[set_index_l2][empty_index_l2].tag = tag_l2;
        cache_l2[set_index_l2][empty_index_l2].lru = 0;
    }
    else // do L2 eviction
    {
        arrCount[L2_Eviction]++;
        int evicted_index_l2 = -1;

        if (cachePolicy == 0) // LRU
        {
            int max_lru = -1;
            for (int i = 0; i < E2; i++)
            {
                if (cache_l2[set_index_l2][i].lru > max_lru)
                {
                    max_lru = cache_l2[set_index_l2][i].lru;
                    evicted_index_l2 = i;
                }
            }
        }
        else // FIFO
        {
            // ...
        }
        printf("%d", empty_index_l2); // =========================================================
        cache_l2[set_index_l2][empty_index_l2].valid = 1;
        cache_l2[set_index_l2][empty_index_l2].tag = tag_l2;
        cache_l2[set_index_l2][empty_index_l2].lru = 0;
    }

    // L1 write-back
    int empty_index = -1; // for L1
    for (int i = 0; i < E; i++)
        if (!cache[set_index][i].valid)
            empty_index = i;

    if (empty_index != -1) // if L1 has vacant space
    {
        cache[set_index][empty_index].valid = 1;
        cache[set_index][empty_index].tag = tag;
        cache[set_index][empty_index].lru = 0;
    }
    else // do L1 eviction
    {
        arrCount[L1_Eviction]++;
        int evicted_index = -1;

        if (cachePolicy == 0) // LRU
        {
            int max_lru = -1;
            for (int i = 0; i < E; i++)
            {
                if (cache[set_index][i].lru > max_lru)
                {
                    max_lru = cache[set_index][i].lru;
                    evicted_index = i;
                }
            }
        }
        else // FIFO
        {
            // ...
        }
        printf("%d", empty_index); // =========================================================
        cache[set_index][empty_index].valid = 1;
        cache[set_index][empty_index].tag = tag;
        cache[set_index][empty_index].lru = 0;
    }

    return; // L1 miss, L2 miss, RAM hit -> L1, L2 write-back, return;
    // ===========================================================================================================================================================
}

/*
 * replayTrace - replays the given trace file against the cache 
 */
void replayTrace(char* trace_fn)
{
    char buf[1000];
    mem_addr_t addr=0;
    unsigned int len=0;
    FILE* trace_fp = fopen(trace_fn, "r");

    if(!trace_fp){
        fprintf(stderr, "%s: %s\n", trace_fn, strerror(errno));
        exit(1);
    }

    while( fgets(buf, 1000, trace_fp) != NULL) {
        if(buf[1]=='S' || buf[1]=='L' || buf[1]=='M') {
            sscanf(buf+3, "%llx,%u", &addr, &len);
      
            if(verbosity)
                printf("%c 0x%llx,%u ", buf[1], addr, len);

	    /* If the instruction contains R */
	    if(buf[1] == 'L' || buf[1] == 'M'){
            	accessData(addr, 0);
	    }

            /* If the instruction contains W */
            if(buf[1]=='M' || buf[1] == 'S')
                accessData(addr, 1);
            
            if (verbosity) 
                printf("\n");
        }
    }

    fclose(trace_fp);
}

/*
 * printUsage - Print usage info
 */
void printUsage(char* argv[])
{
    printf("Usage: %s [-hv] -s <num> -E <num> -b <num> -p <num> -t <file>\n", argv[0]);
    printf("Options:\n");
    printf("  -h         Print this help message.\n");
    printf("  -v         Optional verbose flag.\n");
    printf("  -s <num>   Number of set index bits.\n");
    printf("  -E <num>   Number of lines per set.\n");
    printf("  -b <num>   Number of block offset bits.\n");
    printf("  -p <num>   Select Cache Policy, 0 : lru, 1 : fifo.\n");
    printf("  -t <file>  Trace file.\n");
    printf("\nExamples:\n");
    printf("  linux>  %s -s 4 -E 1 -b 4 -t traces/yi.trace\n", argv[0]);
    printf("  linux>  %s -v -s 8 -E 2 -b 4 -t traces/yi.trace\n", argv[0]);
    exit(0);
}

/*
 * main - Main routine 
 */
int main(int argc, char* argv[])
{
    char c;
    verbosity = 0;
    cachePolicy = 0;
    while( (c=getopt(argc,argv,"s:E:b:t:p:vh")) != -1){
        switch(c){
        case 's':
            s = atoi(optarg);
            break;
        case 'E':
            E = atoi(optarg);
            break;
        case 'b':
            b = atoi(optarg);
            break;
        case 't':
            trace_file = optarg;
            break;
        case 'v':
            verbosity = 1;
            break;
        case 'p':
            cachePolicy = atoi(optarg);
	    break;
        case 'h':
            printUsage(argv);
            exit(0);
        default:
            printUsage(argv);
            exit(1);
        }
    }

    /* Make sure that all required command line args were specified */
    if (s == 0 || E == 0 || b == 0 || trace_file == NULL || !(cachePolicy == 0 || cachePolicy == 1)) {
        printf("%s: Missing required command line argument\n", argv[0]);
        printUsage(argv);
        exit(1);
    }

    s2 = s + 1;

    /* Compute S, E and B from command line args */
    S = (unsigned int) pow(2, s);
    B = (unsigned int) pow(2, b);

    /* Compute S2, E2, and B2 from command line args */
    S2 = (unsigned int) pow(2, s2);
    E2 = (unsigned int) E * 4;
 
    /* Initialize cache */
    initCache();
    
    initCount();

#ifdef DEBUG_ON
    printf("DEBUG: S:%u E:%u B:%u trace:%s\n", S, E, B, trace_file);
    printf("DEBUG: set_index_mask: %llu\n", set_index_mask);
#endif
 
    replayTrace(trace_file);

    /* Free allocated memory */
    freeCache();

    printf("level\t\thits\t\tmisses\t\tevictions\t\t\n");
    printf("L1\t\t%d\t\t%d\t\t%d\t\t\n", arrCount[L1_Hit], arrCount[L1_Miss], arrCount[L1_Eviction]);
    printf("L2\t\t%d\t\t%d\t\t%d\t\t\n", arrCount[L2_Hit], arrCount[L2_Miss], arrCount[L2_Eviction]);
    printf("writes to L2: %d, writes to memory: %d\n", arrCount[L2_Write], arrCount[Mem_Write]);
    
    //printSummary();

    return 0;
}

제가 이 과제를 끝마칠 때까지 이 포스트를 붙잡고 있어야 할 것 같습니다...

살려주세요...

using UnityEngine;
using System.IO;

public class DataManager : MonoBehaviour
{
    public static GameObject data_manager;
    public Data data;

    private void Awake()
    {
        if (data_manager == null)
        {
            data_manager = gameObject;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    private void Start()
    {
        data = new Data();
        LoadFromJson();
    }

    private void OnApplicationQuit()
    {
        SaveToJson();
    }

    public void SaveToJson()
    {
        string filePath = Application.persistentDataPath + "/InGameData.json";
        File.WriteAllText(filePath, JsonUtility.ToJson(data));
    }

    public void LoadFromJson()
    {
        string filePath = Application.persistentDataPath + "/InGameData.json";
        if (File.Exists(filePath) == false)
        {
            ResetData();
            return;
        }
        data = JsonUtility.FromJson<Data>(File.ReadAllText(filePath));
    }

    public void ResetData()
    {
        for (int i = 0; i < 45; i++) data.cleared_levels[i] = false;
    }

    public void UpdateClearedLevel(int cleared_level_index)
    {
        data.cleared_levels[cleared_level_index] = true;
    }
}

[System.Serializable]
public class Data
{
    public bool[] cleared_levels = new bool[45];
}

 

+ Recent posts