Unity:基于C#的定时回调系统(可用于客户端和服务端)

6 篇文章 0 订阅
5 篇文章 0 订阅

本文是学习Siki学院Plane老师的《定时回调系统技术专题》视频课程的学习笔记和总结

实现功能

  1. 支持时间定时,帧定时
  2. 支持任务可循环,可取消,可替换
  3. 使用简单,调用方便

思路:

  • 如何扩展定时任务:将时间计时转为帧数计时
  • 如何扩展取消/替换定时任务:生成唯一id,通过id索引操作任务
  • 如何扩展循环定时任务:通过任务计数运算
  • 如何扩展时间单位支持:统一换算为最小的毫秒运算
  • 如何支持多线程定时任务:通过临时列表进行缓存,错开操作时间;避免使用锁,提升操作效率
  • 如何实现基础定时任务:通过Update()来检测任务条件

 需要注意的问题:

  • 多线程中的线程数据安全问题

 核心代码:

using System;
using System.Collections.Generic;
using System.Timers;

public class PETimer {
    private Action<string> taskLog;
    private Action<Action<int>, int> taskHandle;
    private static readonly string lockTid = "lockTid";
    private DateTime startDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);
    private double nowTime;
    private Timer srvTimer;
    private int tid;
    private List<int> tidLst = new List<int>();
    private List<int> recTidLst = new List<int>();

    private static readonly string lockTime = "lockTime";
    private List<PETimeTask> tmpTimeLst = new List<PETimeTask>();
    private List<PETimeTask> taskTimeLst = new List<PETimeTask>();
    private List<int> tmpDelTimeLst = new List<int>();

    private int frameCounter;

    private static readonly string lockFrame = "lockFrame";
    private List<PEFrameTask> tmpFrameLst = new List<PEFrameTask>();
    private List<PEFrameTask> taskFrameLst = new List<PEFrameTask>();
    private List<int> tmpDelFrameLst = new List<int>();

    public PETimer(int interval = 0) {
        tidLst.Clear();
        recTidLst.Clear();

        tmpTimeLst.Clear();
        taskTimeLst.Clear();

        tmpFrameLst.Clear();
        taskFrameLst.Clear();

        if (interval != 0) {
            srvTimer = new Timer(interval) {
                AutoReset = true
            };

            srvTimer.Elapsed += (object sender, ElapsedEventArgs args) => {
                Update();
            };
            srvTimer.Start();
        }
    }

    public void Update() {
        CheckTimeTask();
        CheckFrameTask();

        DelTimeTask();
        DelFrameTask();

        if (recTidLst.Count > 0) {
            lock (lockTid) {
                RecycleTid();
            }
        }
    }
    private void DelTimeTask() {
        if (tmpDelTimeLst.Count > 0) {
            lock (lockTime) {
                for (int i = 0; i < tmpDelTimeLst.Count; i++) {
                    bool isDel = false;
                    int delTid = tmpDelTimeLst[i];
                    for (int j = 0; j < taskTimeLst.Count; j++) {
                        PETimeTask task = taskTimeLst[j];
                        if (task.tid == delTid) {
                            isDel = true;
                            taskTimeLst.RemoveAt(j);
                            recTidLst.Add(delTid);
                            //LogInfo("Del taskTimeLst ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
                            break;
                        }
                    }

                    if (isDel)
                        continue;

                    for (int j = 0; j < tmpTimeLst.Count; j++) {
                        PETimeTask task = tmpTimeLst[j];
                        if (task.tid == delTid) {
                            tmpTimeLst.RemoveAt(j);
                            recTidLst.Add(delTid);
                            //LogInfo("Del tmpTimeLst ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
                            break;
                        }
                    }
                }
            }
        }
    }
    private void DelFrameTask() {
        if (tmpDelFrameLst.Count > 0) {
            lock (lockFrame) {
                for (int i = 0; i < tmpDelFrameLst.Count; i++) {
                    bool isDel = false;
                    int delTid = tmpDelFrameLst[i];
                    for (int j = 0; j < taskFrameLst.Count; j++) {
                        PEFrameTask task = taskFrameLst[j];
                        if (task.tid == delTid) {
                            isDel = true;
                            taskFrameLst.RemoveAt(j);
                            recTidLst.Add(delTid);
                            break;
                        }
                    }

                    if (isDel)
                        continue;

                    for (int j = 0; j < tmpFrameLst.Count; j++) {
                        PEFrameTask task = tmpFrameLst[j];
                        if (task.tid == delTid) {
                            tmpFrameLst.RemoveAt(j);
                            recTidLst.Add(delTid);
                            break;
                        }
                    }
                }
            }
        }
    }
    private void CheckTimeTask() {
        if (tmpTimeLst.Count > 0) {
            lock (lockTime) {
                //加入缓存区中的定时任务
                for (int tmpIndex = 0; tmpIndex < tmpTimeLst.Count; tmpIndex++) {
                    taskTimeLst.Add(tmpTimeLst[tmpIndex]);
                }
                tmpTimeLst.Clear();
            }
        }

        //遍历检测任务是否达到条件
        nowTime = GetUTCMilliseconds();
        for (int index = 0; index < taskTimeLst.Count; index++) {
            PETimeTask task = taskTimeLst[index];
            if (nowTime.CompareTo(task.destTime) < 0) {
                continue;
            }
            else {
                Action<int> cb = task.callback;
                try {
                    if (taskHandle != null) {
                        taskHandle(cb, task.tid);
                    }
                    else {
                        if (cb != null) {
                            cb(task.tid);
                        }
                    }
                }
                catch (Exception e) {
                    LogInfo(e.ToString());
                }

                //移除已经完成的任务
                if (task.count == 1) {
                    taskTimeLst.RemoveAt(index);
                    index--;
                    recTidLst.Add(task.tid);
                }
                else {
                    if (task.count != 0) {
                        task.count -= 1;
                    }
                    task.destTime += task.delay;
                }
            }
        }
    }
    private void CheckFrameTask() {
        if (tmpFrameLst.Count > 0) {
            lock (lockFrame) {
                //加入缓存区中的定时任务
                for (int tmpIndex = 0; tmpIndex < tmpFrameLst.Count; tmpIndex++) {
                    taskFrameLst.Add(tmpFrameLst[tmpIndex]);
                }
                tmpFrameLst.Clear();
            }
        }

        frameCounter += 1;
        //遍历检测任务是否达到条件
        for (int index = 0; index < taskFrameLst.Count; index++) {
            PEFrameTask task = taskFrameLst[index];
            if (frameCounter < task.destFrame) {
                continue;
            }
            else {
                Action<int> cb = task.callback;
                try {
                    if (taskHandle != null) {
                        taskHandle(cb, task.tid);
                    }
                    else {
                        if (cb != null) {
                            cb(task.tid);
                        }
                    }
                }
                catch (Exception e) {
                    LogInfo(e.ToString());
                }

                //移除已经完成的任务
                if (task.count == 1) {
                    taskFrameLst.RemoveAt(index);
                    index--;
                    recTidLst.Add(task.tid);
                }
                else {
                    if (task.count != 0) {
                        task.count -= 1;
                    }
                    task.destFrame += task.delay;
                }
            }
        }
    }

    #region TimeTask
    public int AddTimeTask(Action<int> callback, double delay, PETimeUnit timeUnit = PETimeUnit.Millisecond, int count = 1) {
        if (timeUnit != PETimeUnit.Millisecond) {
            switch (timeUnit) {
                case PETimeUnit.Second:
                    delay = delay * 1000;
                    break;
                case PETimeUnit.Minute:
                    delay = delay * 1000 * 60;
                    break;
                case PETimeUnit.Hour:
                    delay = delay * 1000 * 60 * 60;
                    break;
                case PETimeUnit.Day:
                    delay = delay * 1000 * 60 * 60 * 24;
                    break;
                default:
                    LogInfo("Add Task TimeUnit Type Error...");
                    break;
            }
        }
        int tid = GetTid(); ;
        nowTime = GetUTCMilliseconds();
        lock (lockTime) {
            tmpTimeLst.Add(new PETimeTask(tid, callback, nowTime + delay, delay, count));
        }
        return tid;
    }
    public void DeleteTimeTask(int tid) {
        lock (lockTime) {
            tmpDelTimeLst.Add(tid);
            //LogInfo("TmpDel ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
        }
        /*
         bool exist = false;

         for (int i = 0; i < taskTimeLst.Count; i++) {
             PETimeTask task = taskTimeLst[i];
             if (task.tid == tid) {
                 //taskTimeLst.RemoveAt(i);
                 for (int j = 0; j < tidLst.Count; j++) {
                     if (tidLst[j] == tid) {
                         //tidLst.RemoveAt(j);
                         break;
                     }
                 }
                 exist = true;
                 break;
             }
         }

         if (!exist) {
             for (int i = 0; i < tmpTimeLst.Count; i++) {
                 PETimeTask task = tmpTimeLst[i];
                 if (task.tid == tid) {
                     //tmpTimeLst.RemoveAt(i);
                     for (int j = 0; j < tidLst.Count; j++) {
                         if (tidLst[j] == tid) {
                             //tidLst.RemoveAt(j);
                             break;
                         }
                     }
                     exist = true;
                     break;
                 }
             }
         }

         return exist;
         */
    }
    public bool ReplaceTimeTask(int tid, Action<int> callback, float delay, PETimeUnit timeUnit = PETimeUnit.Millisecond, int count = 1) {
        if (timeUnit != PETimeUnit.Millisecond) {
            switch (timeUnit) {
                case PETimeUnit.Second:
                    delay = delay * 1000;
                    break;
                case PETimeUnit.Minute:
                    delay = delay * 1000 * 60;
                    break;
                case PETimeUnit.Hour:
                    delay = delay * 1000 * 60 * 60;
                    break;
                case PETimeUnit.Day:
                    delay = delay * 1000 * 60 * 60 * 24;
                    break;
                default:
                    LogInfo("Replace Task TimeUnit Type Error...");
                    break;
            }
        }
        nowTime = GetUTCMilliseconds();
        PETimeTask newTask = new PETimeTask(tid, callback, nowTime + delay, delay, count);

        bool isRep = false;
        for (int i = 0; i < taskTimeLst.Count; i++) {
            if (taskTimeLst[i].tid == tid) {
                taskTimeLst[i] = newTask;
                isRep = true;
                break;
            }
        }

        if (!isRep) {
            for (int i = 0; i < tmpTimeLst.Count; i++) {
                if (tmpTimeLst[i].tid == tid) {
                    tmpTimeLst[i] = newTask;
                    isRep = true;
                    break;
                }
            }
        }

        return isRep;
    }
    #endregion

    #region FrameTask
    public int AddFrameTask(Action<int> callback, int delay, int count = 1) {
        int tid = GetTid();
        lock (lockTime) {
            tmpFrameLst.Add(new PEFrameTask(tid, callback, frameCounter + delay, delay, count));
        }
        return tid;
    }
    public void DeleteFrameTask(int tid) {
        lock (lockFrame) {
            tmpDelFrameLst.Add(tid);
        }
        /*
        bool exist = false;

        for (int i = 0; i < taskFrameLst.Count; i++) {
            PEFrameTask task = taskFrameLst[i];
            if (task.tid == tid) {
                //taskFrameLst.RemoveAt(i);
                for (int j = 0; j < tidLst.Count; j++) {
                    if (tidLst[j] == tid) {
                        //tidLst.RemoveAt(j);
                        break;
                    }
                }
                exist = true;
                break;
            }
        }

        if (!exist) {
            for (int i = 0; i < tmpFrameLst.Count; i++) {
                PEFrameTask task = tmpFrameLst[i];
                if (task.tid == tid) {
                    //tmpFrameLst.RemoveAt(i);
                    for (int j = 0; j < tidLst.Count; j++) {
                        if (tidLst[j] == tid) {
                            //tidLst.RemoveAt(j);
                            break;
                        }
                    }
                    exist = true;
                    break;
                }
            }
        }

        return exist;
        */
    }
    public bool ReplaceFrameTask(int tid, Action<int> callback, int delay, int count = 1) {
        PEFrameTask newTask = new PEFrameTask(tid, callback, frameCounter + delay, delay, count);

        bool isRep = false;
        for (int i = 0; i < taskFrameLst.Count; i++) {
            if (taskFrameLst[i].tid == tid) {
                taskFrameLst[i] = newTask;
                isRep = true;
                break;
            }
        }

        if (!isRep) {
            for (int i = 0; i < tmpFrameLst.Count; i++) {
                if (tmpFrameLst[i].tid == tid) {
                    tmpFrameLst[i] = newTask;
                    isRep = true;
                    break;
                }
            }
        }

        return isRep;
    }
    #endregion

    public void SetLog(Action<string> log) {
        taskLog = log;
    }
    
    public void SetHandle(Action<Action<int>, int> handle) {
        taskHandle = handle;
    }

    public void Reset() {
        tid = 0;
        tidLst.Clear();
        recTidLst.Clear();

        tmpTimeLst.Clear();
        taskTimeLst.Clear();

        tmpFrameLst.Clear();
        taskFrameLst.Clear();

        taskLog = null;
        srvTimer.Stop();
    }

    public int GetYear() {
        return GetLocalDateTime().Year;
    }
    public int GetMonth() {
        return GetLocalDateTime().Month;
    }
    public int GetDay() {
        return GetLocalDateTime().Day;
    }
    public int GetWeek() {
        return (int)GetLocalDateTime().DayOfWeek;
    }
    public DateTime GetLocalDateTime() {
        DateTime dt = TimeZone.CurrentTimeZone.ToLocalTime(startDateTime.AddMilliseconds(nowTime));
        return dt;
    }
    public double GetMillisecondsTime() {
        return nowTime;
    }
    public string GetLocalTimeStr() {
        DateTime dt = GetLocalDateTime();
        string str = GetTimeStr(dt.Hour) + ":" + GetTimeStr(dt.Minute) + ":" + GetTimeStr(dt.Second);
        return str;
    }

    #region Tool Methonds
    private int GetTid() {
        lock (lockTid) {
            tid += 1;

            //安全代码,以防万一
            while (true) {
                if (tid == int.MaxValue) {
                    tid = 0;
                }

                bool used = false;
                for (int i = 0; i < tidLst.Count; i++) {
                    if (tid == tidLst[i]) {
                        used = true;
                        break;
                    }
                }
                if (!used) {
                    tidLst.Add(tid);
                    break;
                }
                else {
                    tid += 1;
                }
            }
        }

        return tid;
    }
    private void RecycleTid() {
        for (int i = 0; i < recTidLst.Count; i++) {
            int tid = recTidLst[i];

            for (int j = 0; j < tidLst.Count; j++) {
                if (tidLst[j] == tid) {
                    tidLst.RemoveAt(j);
                    break;
                }
            }
        }
        recTidLst.Clear();
    }
    private void LogInfo(string info) {
        if (taskLog != null) {
            taskLog(info);
        }
    }
    private double GetUTCMilliseconds() {
        TimeSpan ts = DateTime.UtcNow - startDateTime;
        return ts.TotalMilliseconds;
    }
    private string GetTimeStr(int time) {
        if (time < 10) {
            return "0" + time;
        }
        else {
            return time.ToString();
        }
    }
    #endregion

    class PETimeTask {
        public int tid;
        public Action<int> callback;
        public double destTime;//单位:毫秒
        public double delay;
        public int count;

        public PETimeTask(int tid, Action<int> callback, double destTime, double delay, int count) {
            this.tid = tid;
            this.callback = callback;
            this.destTime = destTime;
            this.delay = delay;
            this.count = count;
        }
    }

    class PEFrameTask {
        public int tid;
        public Action<int> callback;
        public int destFrame;
        public int delay;
        public int count;

        public PEFrameTask(int tid, Action<int> callback, int destFrame, int delay, int count) {
            this.tid = tid;
            this.callback = callback;
            this.destFrame = destFrame;
            this.delay = delay;
            this.count = count;
        }
    }
}

public enum PETimeUnit {
    Millisecond,
    Second,
    Minute,
    Hour,
    Day
}

使用示例代码

PETimer控制台工程案例代码:

using System;
using System.Threading;
using System.Collections.Generic;

namespace ConsoleProjects {
    class Program {
        private static readonly string obj = "lock";

        static void Main(string[] args) {
            Console.WriteLine("Test Start!");
            //Test1();
            Test2();
        }

        //第一种用法:运行线程检测并处理任务
        static void Test1() {
            //运行线程驱动计时
            PETimer pt = new PETimer();
            pt.SetLog((string info) => {
                Console.WriteLine("LogInfo:" + info);
            });

            pt.AddTimeTask((int tid) => {
                Console.WriteLine("Process线程ID:{0}", Thread.CurrentThread.ManagedThreadId.ToString());
            }, 10, PETimeUnit.Millisecond, 0);

            while (true) {
                pt.Update();
            }
        }

        //第二种用法:独立线程检测并处理任务
        static void Test2() {
            Queue<TaskPack> tpQue = new Queue<TaskPack>();
            //独立线程驱动计时
            PETimer pt = new PETimer(5);
            pt.SetLog((string info) => {
                Console.WriteLine("LogInfo:" + info);
            });


            int id = pt.AddTimeTask((int tid) => {
                Console.WriteLine("Process线程ID:{0}", Thread.CurrentThread.ManagedThreadId.ToString());
            }, 3000, PETimeUnit.Millisecond, 0);

            //设置回调处理器
            /*
            pt.SetHandle((Action<int> cb, int tid) => {
                if (cb != null) {
                    lock (obj) {
                        tpQue.Enqueue(new TaskPack(tid, cb));
                    }
                }
            });
            */
            while (true) {
                string ipt = Console.ReadLine();
                if (ipt == "a") {
                    pt.DeleteTimeTask(id);
                }


                if (tpQue.Count > 0) {
                    TaskPack tp = null;
                    lock (obj) {
                        tp = tpQue.Dequeue();
                    }
                    tp.cb(tp.tid);
                }
            }
        }
    }

    //任务数据包
    class TaskPack {
        public int tid;
        public Action<int> cb;
        public TaskPack(int tid, Action<int> cb) {
            this.tid = tid;
            this.cb = cb;
        }
    }
}

PETimer集成到Unity案例代码:

using UnityEngine;

public class GameStart : MonoBehaviour {
    PETimer pt = new PETimer();

    int tempID = -1;
    private void Start() {
        //时间定时
        pt.AddTimeTask(TimerTask, 500, PETimeUnit.Millisecond, 3);
        //帧数定时
        pt.AddFrameTask(FrameTask, 100, 3);

        //定时替换/删除
        tempID = pt.AddTimeTask((int tid) => {
            Debug.Log("定时等待替换......");
        }, 1, PETimeUnit.Second, 0);
    }

    private void Update() {
        pt.Update();

        //定时替换
        if (Input.GetKeyDown(KeyCode.R)) {

            bool succ = pt.ReplaceTimeTask(tempID, (int tid) => {
                Debug.Log("定时等待删除......");
            }, 2, PETimeUnit.Second, 0);

            if (succ) {
                Debug.Log("替换成功");
            }
        }

        //定时删除
        if (Input.GetKeyDown(KeyCode.D)) {
            pt.DeleteTimeTask(tempID);
        }
    }

    void TimerTask(int tid) {
        Debug.Log("TimeTask:" + System.DateTime.UtcNow);
    }

    void FrameTask(int tid) {
        Debug.Log("FrameTask:" + System.DateTime.UtcNow);
    }
}

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在Unity中实现本地客户服务之间发送图片,可以使用Unity自带的网络库UNET。具体步骤如下: 1. 创建一个空的Unity项目,然后在Unity编辑器中选择“Assets”->“Import Package”->“Custom Package”,导入UNET网络库。 2. 创建一个新的场景,并在场景中创建一个空对象,命名为“NetworkManager”。 3. 在“NetworkManager”对象上添加“NetworkManager”组件,并设置“Network Address”为“LocalHost”(表示连接到本地服务器)。 4. 在“NetworkManager”对象上添加“NetworkManagerHUD”组件,这将允许你在游戏运行时连接到本地服务器,并显示连接状态。 5. 在场景中创建一个新的空对象,命名为“MyNetworkManager”。 6. 在“MyNetworkManager”对象上添加以下脚本: ```csharp using UnityEngine; using UnityEngine.Networking; public class MyNetworkManager : NetworkManager { public void SendImage(byte[] data) { // 创建一个网络消息对象,用于发送图片数据 ImageMessage msg = new ImageMessage(); msg.imageData = data; // 向所有客户发送消息 NetworkServer.SendToAll(MsgType.Custom, msg); } } // 自定义网络消息类型,用于发送图片数据 public class ImageMessage : MessageBase { public byte[] imageData; } ``` 7. 在场景中创建一个新的空对象,命名为“ImageSender”。 8. 在“ImageSender”对象上添加以下脚本: ```csharp using UnityEngine; using UnityEngine.UI; using UnityEngine.Networking; using System.IO; public class ImageSender : MonoBehaviour { public MyNetworkManager networkManager; public RawImage image; public Button sendButton; // Start is called before the first frame update void Start() { sendButton.onClick.AddListener(OnSendButtonClick); } // 发送按钮点击事件 void OnSendButtonClick() { // 将RawImage中的图片数据转换为PNG格式 Texture2D tex = (Texture2D)image.texture; byte[] data = tex.EncodeToPNG(); // 用网络管理器发送图片数据 networkManager.SendImage(data); } } ``` 9. 在场景中创建一个新的空对象,命名为“ImageReceiver”。 10. 在“ImageReceiver”对象上添加以下脚本: ```csharp using UnityEngine; using UnityEngine.UI; using UnityEngine.Networking; using System.IO; public class ImageReceiver : NetworkBehaviour { public RawImage image; // 启动时注册网络消息类型 void Start() { NetworkManager.singleton.client.RegisterHandler(MsgType.Custom, OnReceiveImage); } // 接收到图片数据后的处理函数 void OnReceiveImage(NetworkMessage netMsg) { // 解析网络消息,获取图片数据 ImageMessage msg = netMsg.ReadMessage<ImageMessage>(); byte[] data = msg.imageData; // 创建Texture2D对象,并从数据中加载图片 Texture2D tex = new Texture2D(1, 1); tex.LoadImage(data); // 将图片显示在RawImage组件中 image.texture = tex; } } ``` 现在你已经完成了客户服务的代码编写。在场景中添加一个RawImage组件,用于显示图片。然后将“ImageSender”和“ImageReceiver”对象拖到场景中,并将“NetworkManager”对象拖到“ImageSender”的“networkManager”属性中。 运行游戏,并点击“ImageSender”对象上的“sendButton”按钮,你将看到RawImage组件中显示了发送的图片。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值