[feature] First Update

This commit is contained in:
lichx 2024-12-04 22:01:33 +08:00
commit 2d81ced29a
33 changed files with 4056 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

3
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

5
README.md Normal file
View File

@ -0,0 +1,5 @@
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>操作系统实验</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

2502
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

26
package.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "operating-system",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"echarts": "^5.5.1",
"pinia": "^2.2.6",
"tdesign-vue-next": "^1.10.4",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue-jsx": "^4.1.1",
"less": "^4.2.1",
"typescript": "~5.6.2",
"vite": "^6.0.1",
"vue-tsc": "^2.1.10"
}
}

1
public/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

(image error) Size: 1.5 KiB

30
src/App.vue Normal file
View File

@ -0,0 +1,30 @@
<script setup lang="ts">
</script>
<template>
<header>
<RouterLink to="/expr1" class="link-button">
<t-button>
<p>实验一</p>
</t-button>
</RouterLink>
<RouterLink to="/expr2" class="link-button">
<t-button>
实验二
</t-button>
</RouterLink>
</header>
<main>
<RouterView/>
</main>
</template>
<style scoped lang="less">
.link-button {
margin-right: 5px;
}
header{
margin-bottom: 20px;
}
</style>

BIN
src/assets/Disk.png Normal file

Binary file not shown.

After

(image error) Size: 15 KiB

BIN
src/assets/Pin.png Normal file

Binary file not shown.

After

(image error) Size: 7.8 KiB

29
src/components/expr1.vue Normal file
View File

@ -0,0 +1,29 @@
<script setup lang="tsx">
import RAM from "@/components/expr1/RAM.vue";
import OperationTable from "@/components/expr1/OperationTable.vue";
import {provide} from "vue";
import {Ram} from "@/logical/expr1/ram.ts";
import ControlPanel from "@/components/expr1/ControlPanel.vue";
let ram = new Ram();
provide("ram", ram);
</script>
<template>
<control-panel/>
<RAM/>
<div id="operation-table">
<operation-table/>
</div>
</template>
<style scoped lang="less">
#operations {
width: 690px;
}
#operation-table {
margin-top: 10px;
}
</style>

View File

@ -0,0 +1,34 @@
<script setup lang="ts">
import useExpr1Store from "@/store/expr1/expr1Store.ts";
import {storeToRefs} from "pinia";
import {Ram} from "@/logical/expr1/ram.ts";
import {inject} from "vue";
let {tableChanged, failInfo, memSize} = storeToRefs(useExpr1Store());
let ram: Ram = inject("ram")!;
function onChange(checkedValues: string) {
ram.CurrentSelector = parseInt(checkedValues);
tableChanged.value = true;
}
function onMemoryChange() {
tableChanged.value = true;
}
</script>
<template>
<t-space direction="horizontal" :size="22">
<t-typography-text>内存大小</t-typography-text>
<t-input-number @change="onMemoryChange" v-model:value="memSize" theme="column"></t-input-number>
<t-radio-group default-value="0" @change="onChange">
<t-radio value="0">FirstFit</t-radio>
<t-radio value="1">BestFit</t-radio>
<t-radio value="2">WorstFit</t-radio>
</t-radio-group>
<t-typography-text theme="error">{{ failInfo }}</t-typography-text>
</t-space>
</template>
<style scoped lang="less">
</style>

View File

@ -0,0 +1,239 @@
<script setup lang="tsx">
import useExpr1Store from "@/store/expr1/expr1Store.ts";
import {storeToRefs} from "pinia";
import {Input, RowClassNameParams, Select} from "tdesign-vue-next";
import {Operation} from "@/store/expr1/defaultOperation.ts";
import {computed, inject} from "vue";
import {Ram} from "@/logical/expr1/ram.ts";
const typeListMap = {
'request': {
label: '申请',
theme: 'success',
},
'release': {
label: '释放',
theme: 'primary',
},
}
let expr1Store = useExpr1Store();
let ram: Ram = inject("ram")!;
let beforeKey = -1;
const {operations, tableChanged, currentState} = storeToRefs(expr1Store);
const data = computed(() => {
return operations.value.map((operation: Operation, index: number) => {
return {
key: String(index + 1),
id: operation.id,
type: operation.type,
memorySize: operation.type === "release" ? ram.GetReleaseHistory(operation.id) : operation.memorySize,
}
});
})
function OnTableBlur(row: number, col: number, value: number | string) {
if (col === 1 || col === 3) {
if (typeof value === 'string')
value = parseInt(value as string);
if (value == undefined || (value as number) < 0) {
return;
}
}
switch (col) {
case 1:
operations.value[row].id = value as number;
break;
case 2:
operations.value[row].type = value as "request" | "release";
break;
case 3:
if (operations.value[row].type === 'request')
operations.value[row].memorySize = value as number;
break;
}
tableChanged.value = true;
}
const columns = [
{
title: '任务编号',
colKey: 'key',
className: 'id-col',
},
{
title: '进程编号',
colKey: 'id',
className: 'process-id-col',
edit: {
component: Input,
props: {
clearable: true,
autofocus: true,
autoWidth: true,
},
rules: [
{
required: true,
message: '不能为空',
},
{
pattern: /^[+-]?(\d+(\.\d*)?|\.\d+)$/,
message: '请输入数字',
},
{
pattern: /^\d+(\.\d+)?$/,
message: '请输入正数',
}
],
on: ({rowIndex, colIndex}: {
rowIndex: number,
colIndex: number,
}) => ({
onBlur: (value: number) => OnTableBlur(rowIndex, colIndex, value)
})
}
},
{
title: '操作类型', colKey: 'type', cell: (_: any, {row}: RowClassNameParams<Operation>) => {
return (
<t-tag shape="round" theme={typeListMap[row.type].theme} variant="light-outline">
{typeListMap[row.type].label}
</t-tag>
);
},
edit: {
component: Select,
props: {
clearable: true,
autofocus: true,
autoWidth: true,
options: [
{label: '申请', value: 'request'},
{label: '释放', value: 'release'},
]
},
rules: [
{
required: true,
message: '不能为空',
},
],
on: ({rowIndex, colIndex}: {
rowIndex: number,
colIndex: number,
}) => ({
onChange: ({value}: {
value: string
}) => OnTableBlur(rowIndex, colIndex, value)
}
)
}
},
{
title: '涉及内存大小',
colKey: 'memorySize',
className: 'memory-size',
edit: {
component: Input,
props: {
clearable: true,
autofocus: true,
autoWidth: true,
},
rules: [
{
required: true,
message: '不能为空',
},
{
pattern: /^[+-]?(\d+(\.\d*)?|\.\d+)$/,
message: '请输入数字',
},
{
pattern: /^\d+(\.\d+)?$/,
message: '请输入正数',
}
],
on: ({rowIndex, colIndex}: {
rowIndex: number,
colIndex: number,
}) => ({
onBlur: (value: number) => OnTableBlur(rowIndex, colIndex, value)
})
}
},
{
colKey: 'operation',
title: '操作',
width: 140
}
];
function GetRowName(params: RowClassNameParams<Operation>): string {
const row = params.row;
return row.type === 'request' ? 'request-row' : 'release-row';
}
function MouseEnter({row}: { row: { key: string } }) {
beforeKey = currentState.value;
currentState.value = parseInt(row.key);
//console.log(detailMemoryIndex.value)
}
function MouseLeave() {
currentState.value = beforeKey;
}
function MouseClick({row}: { row: { key: string } }) {
currentState.value = -1;
beforeKey = parseInt(row.key);
currentState.value = beforeKey;
}
function InsertBefore(row: number) {
operations.value.splice(row - 1, 0, {
id: 0,
type: 'request',
memorySize: 0,
});
tableChanged.value = true;
}
function InsertEnd() {
operations.value.push({
id: 0,
type: 'request',
memorySize: 0,
});
tableChanged.value = true;
}
function Delete(row: number) {
operations.value.splice(row - 1, 1);
tableChanged.value = true;
}
</script>
<template>
<div id="operations">
<t-table row-key="key" :columns="columns" :data="data" :hover="true" :row-class-name="GetRowName" :stripe="true"
:resizable=true @row-mouseenter="MouseEnter" @row-mouseleave="MouseLeave" @row-mousedown="MouseClick"
:column-controller="{displayType: 'auto-width',hideTriggerButton: true}">
<template #operation="{ row }">
<t-link style="display: inline" theme="primary" hover="color" @click="InsertBefore(parseInt(row.key))">
在之前插入
</t-link>
<t-link theme="primary" hover="color" @click.stop="Delete(parseInt(row.key))">
删除
</t-link>
</template>
</t-table>
<t-button @click="InsertEnd">新增一条操作</t-button>
</div>
</template>
<style scoped lang="less">
</style>

View File

@ -0,0 +1,115 @@
<script setup lang="ts">
import {Block, Ram} from "@/logical/expr1/ram.ts";
import {computed, inject} from "vue";
import useExpr1Store from "@/store/expr1/expr1Store.ts";
import {storeToRefs} from "pinia";
interface RamBlock extends Block {
Index: number;
Flex: number;
StartFrom: number;
}
let ram: Ram = inject("ram")!;
const {detailMemoryIndex, currentState} = storeToRefs(useExpr1Store());
let startFrom = 0;
let blocks = computed(() => {
let memory = ram.GetMemory();
let blocks: RamBlock[] = [];
for (let i = 0; i < memory.length; i++) {
blocks.push({
...memory[i],
Index: i,
Flex: memory[i].MemorySize / ram._memSize.value,
StartFrom: startFrom
});
startFrom += memory[i].MemorySize;
}
return blocks;
})
</script>
<template>
<p>CurrentState: {{ currentState }}</p>
<div class="memory">
<div v-for="block in blocks" :key="block.Index" :style="{flex: block.Flex}" class="block"
:class="block.Type" @mouseenter="detailMemoryIndex = block.Index"
@mouseleave="detailMemoryIndex = -1">
<p v-if="block.Type === 'Process'">Process {{ block.ProcessID }}<br>{{ block.MemorySize }} </p>
<p v-else></p>
<div v-if="block.Index === detailMemoryIndex" class="tip"
@mouseover="detailMemoryIndex = block.Index">
<span v-if="block.Type==='Process'">Process {{
block.ProcessID
}}<br>Start From: {{ block.StartFrom }}<br>Space: {{ block.MemorySize }}</span>
<span v-else>Free Space<br>Start From: {{ block.StartFrom }}<br>Space: {{ block.MemorySize }}</span>
</div>
</div>
</div>
</template>
<style scoped lang="less">
.fade-move, .fade-enter-active, .fade-leave-active {
transition: opacity 2s;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
.fade-leave-active {
position: absolute;
}
.memory {
margin-top: 70px;
display: flex;
justify-content: center;
align-items: center;
height: 200px;
width: 80vw;
background-color: #181818;
text-align: center;
}
.Process {
border: #cccaff 1px solid;
height: 100%;
}
.Process p {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
height: 100%;
margin: 0;
}
.block {
position: relative;
white-space: nowrap;
overflow: hidden;
}
.Space {
background: repeating-linear-gradient(45deg, /* Angle of the stripes */ #cccaff, /* Color 1 */ #cccaff 10px, /* Color 1 stop */ #000000 10px, /* Color 2 start */ #000000 20px /* Color 2 stop */);
height: 100%;
border: #cccaff 1px solid;
}
.tip {
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background-color: #333;
color: #fff;
padding: 5px;
border-radius: 3px;
white-space: nowrap;
z-index: 20;
}
</style>

24
src/components/expr2.vue Normal file
View File

@ -0,0 +1,24 @@
<script setup lang="ts">
import DISK from "@/components/expr2/DISK.vue";
import OperationTable from "@/components/expr2/OperationTable.vue";
import OperationGraph from "@/components/expr2/OperationGraph.vue";
import ControlPanel from "@/components/expr2/ControlPanel.vue";
import {Disk} from "@/logical/expr2/disk.ts";
import {provide} from "vue";
provide("disk",new Disk());
</script>
<template>
<ControlPanel/>
<div style="display: flex">
<DISK/>
<OperationGraph/>
</div>
<OperationTable/>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,34 @@
<script setup lang="ts">
import {storeToRefs} from "pinia";
import useExpr2Store from "@/store/expr2/expr2Store.ts";
let {tableChanged, failInfo, trackCount, algorithm, startTrack} = storeToRefs(useExpr2Store());
function onChange(checkedValues: string) {
algorithm.value = parseInt(checkedValues);
tableChanged.value = true;
}
function onTrackChange() {
tableChanged.value = true;
}
</script>
<template>
<t-space direction="horizontal" :size="22">
<t-typography-text>磁道数量</t-typography-text>
<t-input-number @change="onTrackChange" v-model:value="trackCount" theme="column"></t-input-number>
<t-typography-text>起始磁道</t-typography-text>
<t-input-number @change="onTrackChange" v-model:value="startTrack" theme="column"></t-input-number>
<t-radio-group default-value="0" @change="onChange">
<t-radio value="0">FCFS</t-radio>
<t-radio value="1">SSTF</t-radio>
<t-radio value="2">SCAN_FirstDown</t-radio>
<t-radio value="3">SCAN_FirstUp</t-radio>
</t-radio-group>
<t-typography-text theme="error">{{ failInfo }}</t-typography-text>
</t-space>
</template>
<style scoped lang="less">
</style>

View File

@ -0,0 +1,131 @@
<script setup lang="ts">
import {
computed, ref, watch
} from "vue";
import {storeToRefs} from "pinia";
import useExpr2Store from "@/store/expr2/expr2Store.ts";
//18 <-> -12
const duration = 1, animationCount = 40;
const {currentTrack, trackCount, trackFromIndex, trackToIndex, operations, startTrack,selectTrackFromIndex,selectTrackToIndex} = storeToRefs(useExpr2Store());
const degree = computed(() =>
6 - (currentTrack.value / trackCount.value * (30) - 12)
);
const rotate = ref(0);
const cssVars = computed(() => {
// console.log({"--rotate": `${rotate.value}deg`});
return ({"--rotate": `${rotate.value}deg`});
});
const trackDelta = computed(() => {
let start = trackFromIndex.value === -1 ? startTrack.value : operations.value[trackFromIndex.value].track;
let end = trackToIndex.value === -1 ? startTrack.value : operations.value[trackToIndex.value].track;
return end - start;
});
const selectTrackSum = computed(()=>{
if(selectTrackFromIndex.value === selectTrackToIndex.value)
return 0;
let t = 0;
let last = selectTrackFromIndex.value === -1? selectTrackToIndex.value: operations.value[selectTrackFromIndex.value].track;
console.log(selectTrackFromIndex.value,selectTrackToIndex.value,t);
for(let i = Math.max(0,selectTrackFromIndex.value);i<=selectTrackToIndex.value;i++)
{
console.log(operations.value[i].track);
t += Math.abs(operations.value[i].track - last);
last = operations.value[i].track;
}
return t;
})
let token: number = -1;
watch(degree, (newDeg) => {
if (token !== -1) {
clearInterval(token);
}
let state = 0;
let start = rotate.value;
let end = newDeg;
let v = 0;
//a = 2x/t^2 = 2 * ((end - start) / 2) / (duration / 2) / (duration / 2)
// = 2 * (end - start) / duration^2
let a = 4 * (end - start) / duration / duration;
token = setInterval(() => {
// console.log(rotate.value);
v = v + a * duration / animationCount * (state >= animationCount / 2 ? -1 : 1);
state++;
rotate.value = rotate.value + v * duration / animationCount;
// console.log(state);
if (state >= animationCount) {
rotate.value = end;
clearInterval(token);
token = -1;
}
}, duration / animationCount * 1000);
}, {immediate: true});
</script>
<template>
<div id="disk">
<t-image src="/src/assets/Disk.png" fit="cover" :style="{ width: '470px', height: '487px' }" position="right"/>
<div id="pin" :style="cssVars">
<t-image :key="cssVars" src="/src/assets/Pin.png" fit="cover"
:style="{ width: '206px', height: '240px', background: 'transparent' }"
position="right"/>
</div>
<div id="track-delta">
<t-typography-text style="color:#cccaff;">{{
trackDelta === 0 ? 0 : (trackDelta > 0 ? '+' + trackDelta : trackDelta)
}}
</t-typography-text>
</div>
<div id="track-sum">
<t-typography-text style="color:#cccaff;">Sum: {{ selectTrackSum }}
</t-typography-text>
</div>
</div>
</template>
<style scoped lang="less">
#disk {
width: 470px;
height: 487px;
position: relative;
}
#disk::after {
position: absolute;
content: "";
top: 0;
left: 0;
height: 100%;
width: 100%;
box-shadow: inset 0px -20px 15px -12px #242424,
inset 0px 20px 15px -12px #242424;
z-index: 10;
}
@rotate: var(--rotate, 0deg);
#pin {
position: absolute;
top: 218px;
left: 150px;
z-index: 1;
transform-origin: 50px 191px;
rotate: @rotate;
}
#track-delta {
z-index: 2;
position: absolute;
top: 396px;
left: 210px;
}
#track-sum {
z-index: 2;
position: absolute;
top: 396px;
left: 270px;
}
</style>

View File

@ -0,0 +1,76 @@
<script setup lang="ts">
import * as echarts from 'echarts';
import {computed, onMounted, ref, watch} from 'vue';
import {storeToRefs} from 'pinia';
import useExpr2Store from '@/store/expr2/expr2Store.ts';
const chartRef = ref(null);
const expr2Store = useExpr2Store();
const {operations, dataChanged,startTrack} = storeToRefs(expr2Store);
let opers = computed(()=>[{track: startTrack.value, time: 0},...operations.value]);
onMounted(() => {
const chart = echarts.init(chartRef.value);
const option = {
title: {
text: ' Mechanical Hard Drive Seek Time Diagram'
},
tooltip: {
trigger: 'axis',
formatter: (params: { data: number[] }[]) => {
const {data} = params[0];
return `Time: ${data[1]}<br/>Track: ${data[0]}`;
}
},
xAxis: {
type: 'value',
name: 'Track',
position: 'top',
splitLine: {
show: false
},
},
yAxis: {
type: 'value',
name: 'Time',
inverse: true,
splitLine: {
show: false
},
axisLine: {
symbol: ['arrow', 'none']
}
},
series: [
{
data: opers.value.map((operation, index) => [operation.track, index]),
type: 'line',
smooth: false
}
]
};
chart.setOption(option);
watch(dataChanged, (newVal) => {
if (newVal === false)
return;
chart.setOption(({
series: [
{
data: opers.value.map((operation, index) => [operation.track, index]),
type: 'line',
smooth: false
}
]
}));
dataChanged.value = false;
});
});
</script>
<template>
<div ref="chartRef" style="width: 600px; height: 400px;"></div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,208 @@
<script setup lang="tsx">
import {storeToRefs} from "pinia";
import {Input} from "tdesign-vue-next";
import {computed} from "vue";
import useExpr2Store from "@/store/expr2/expr2Store.ts";
import {Operation} from "@/store/expr2/defaultOperation.ts";
let expr2Store = useExpr2Store();
let {
startTrack,
operations,
originOperations,
tableChanged,
currentTrack,
trackFromIndex,
trackToIndex,
selectTrackFromIndex,
selectTrackToIndex
} = storeToRefs(expr2Store);
let data = computed(() => {
console.log(startTrack.value);
return [{track: startTrack.value, id: 0}, ...operations.value].map((operation: Operation, index: number) => {
return {
key: String(index + 1),
id: operation.id,
track: operation.track,
}
});
});
let beforeKey = -1;
let beforeFromKey = -1;
let beforeToKey = -1;
function FindOriginOperationIndex(key: string): number {
let index = parseInt(key) - 1;
index = data.value[index].id;
console.log("index", index)
if (index === 0)
return -1;
for (let i = 0; i < originOperations.value.length; i++) {
if (originOperations.value[i].id === index)
return i;
}
return -1;
}
function OnTableBlur(col: number, {key: editedRowKey}: { key: string }, value: number | string) {
console.log(editedRowKey);
let index = FindOriginOperationIndex(editedRowKey);
console.log(index, col, value)
if (index === -1) {
if (col === 2) {
startTrack.value = value as number;
tableChanged.value = true;
}
return;
}
switch (col) {
case 1:
originOperations.value[index].id = value as number;
break;
case 2:
originOperations.value[index].track = value as number;
break;
}
tableChanged.value = true;
}
const columns = [
{
title: '序号',
colKey: 'key'
},
{
title: '任务编号',
colKey: 'id',
edit: {
component: Input,
props: {
clearable: true,
autofocus: true,
autoWidth: true,
},
rules: [
{
required: true,
message: '不能为空',
},
{
pattern: /^\d+$/,
message: '请输入数字',
}],
on: ({colIndex, editedRow}: {
colIndex: number,
editedRow: { key: string }
}) => ({
onBlur: (value: number) => OnTableBlur(colIndex, editedRow, value)
})
}
},
{
title: '目标磁道',
colKey: 'track',
edit: {
component: Input,
props: {
clearable: true,
autofocus: true,
autoWidth: true,
},
rules: [
{
required: true,
message: '不能为空',
},
{
pattern: /^\d+$/,
message: '请输入数字',
}],
on: ({colIndex, editedRow}: {
colIndex: number,
editedRow: { key: string }
}) => ({
onBlur: (value: number) => OnTableBlur(colIndex, editedRow, value)
})
}
},
{
colKey: 'operation',
title: '操作',
width: 140
}
];
function MouseEnter({row, index}: { row: { track: number }, index: number }) {
beforeKey = currentTrack.value;
beforeFromKey = trackFromIndex.value;
beforeToKey = trackToIndex.value;
currentTrack.value = row.track;
trackFromIndex.value = Math.max(-1, index - 2);
trackToIndex.value = index - 1;
}
function MouseLeave() {
currentTrack.value = beforeKey;
}
// function MouseClick({row}: { row: { track: number } }) {
// currentTrack.value = -1;
// beforeKey = row.track;
// currentTrack.value = beforeKey;
// }
function ActiveChange(rows: string[]) {
console.log(rows);
if (rows.length === 0)
return;
selectTrackFromIndex.value = parseInt(rows[0]) - 2;
selectTrackToIndex.value = parseInt(rows[rows.length - 1]) - 2;
}
// row Error
function InsertBefore(row: number) {
originOperations.value.splice(row - 1, 0, {
id: 0,
track: -1
});
tableChanged.value = true;
}
function InsertEnd() {
originOperations.value.push({
id: 0,
track: -1
});
tableChanged.value = true;
}
// row Error
function Delete(row: number) {
originOperations.value.splice(row - 1, 1);
tableChanged.value = true;
}
</script>
<template>
<div id="operations">
<t-table row-key="key" :columns="columns" :data="data" :hover="true"
:resizable=true @row-mouseenter="MouseEnter" @row-mouseleave="MouseLeave" @active-change="ActiveChange"
:column-controller="{displayType: 'auto-width',hideTriggerButton: true}" :active-row-type="'single'">
<template #operation="{ row }">
<t-link style="display: inline" theme="primary" hover="color" @click="InsertBefore(parseInt(row.key))">
在之前插入
</t-link>
<t-link theme="primary" hover="color" @click.stop="Delete(parseInt(row.key))">
删除
</t-link>
</template>
</t-table>
<t-button @click="InsertEnd">新增一条操作</t-button>
</div>
</template>
<style scoped lang="less">
</style>

206
src/logical/expr1/ram.ts Normal file
View File

@ -0,0 +1,206 @@
import {Ref, ref, watch} from "vue";
import useExpr1Store from "@/store/expr1/expr1Store.ts";
import {storeToRefs} from "pinia";
import {Operation} from "@/store/expr1/defaultOperation.ts";
export enum Selector {
FirstFit,
BestFit,
WorstFit
}
export class Ram {
_memSize: Ref<number, number>;
_memory: Ref<Block[], Block[]>;
_operations: Ref<Operation[], Operation[]>;
_selectors: ((operation: Operation) => number)[];
_currentSelector: number = 0;
_nextOperation: number = 0;
_currentState: Ref<number, number>;
_allStates: Ref<Block[][], Block[][]> = ref([]);
_releaseHistory: Map<number, number>
static OperationToProcess(operation: Operation): Block {
return new Block(operation.id, operation.memorySize);
}
constructor() {
let store = useExpr1Store();
let {operations, memSize, tableChanged} = storeToRefs(store);
this._operations = operations;
this._memSize = memSize;
this._memory = ref([new Block(0, memSize.value, "Space")]);
this._selectors = [this.FirstFit.bind(this), this.BestFit.bind(this), this.WorstFit.bind(this)];
this._currentState = storeToRefs(store).currentState;
this._allStates.value.push(this._memory.value.map(x => x));
this._releaseHistory = new Map();
let {failInfo} = storeToRefs(store);
while (this._nextOperation < operations.value.length) {
this.Process();
this._allStates.value.push(this._memory.value.map(x => Block.Clone(x)));
}
watch(tableChanged, (val) => {
if (!val)
return;
this._nextOperation = 0;
this._allStates.value = [];
failInfo.value = "";
this._releaseHistory = new Map();
this._memory.value = [new Block(0, memSize.value, "Space")];
this._allStates.value.push(this._memory.value.map(x => Block.Clone(x)));
while (this._nextOperation < this._operations.value.length) {
try {
this.Process();
this._allStates.value.push(this._memory.value.map(x => Block.Clone(x)));
} catch (e:any) {
e = e as Error;
console.log(this._nextOperation, e);
failInfo.value = "任务" + this._nextOperation + '\t' +e.message;
while (this._nextOperation < operations.value.length) {
this._allStates.value.push(this._memory.value.map(x => Block.Clone(x)));
this._nextOperation++;
}
break;
}
}
tableChanged.value = false;
});
}
set CurrentSelector(value: Selector) {
this._currentSelector = value;
console.log(this._currentSelector);
}
GetMemory() {
if (this._currentState.value >= this._allStates.value.length)
this._currentState.value = this._allStates.value.length - 1;
return this._allStates.value[this._currentState.value];
}
GetReleaseHistory(processID: number) {
if (this._releaseHistory.has(processID))
return this._releaseHistory.get(processID)!;
else
return '-';
}
FirstFit(operation: Operation): number {
for (let i = 0; i < this._memory.value.length; i++) {
if (this._memory.value[i].Type === "Process") {
continue;
}
if (this._memory.value[i].MemorySize >= operation.memorySize) {
return i;
}
}
return -1;
}
BestFit(operation: Operation): number {
let memories: Map<number, number> = new Map();
for (let i = 0; i < this._memory.value.length; i++) {
if (this._memory.value[i].Type === "Process") {
continue;
}
memories.set(i, this._memory.value[i].MemorySize);
}
let sorted = Array.from(memories).sort((a, b) => a[1] - b[1]);
for (let i = 0; i < sorted.length; i++) {
if (sorted[i][1] >= operation.memorySize) {
return sorted[i][0];
}
}
return -1;
}
WorstFit(operation: Operation): number {
let memories: Map<number, number> = new Map();
for (let i = 0; i < this._memory.value.length; i++) {
if (this._memory.value[i].Type === "Process") {
continue;
}
memories.set(i, this._memory.value[i].MemorySize);
}
let sorted = Array.from(memories).sort((a, b) => b[1] - a[1]);
for (let i = 0; i < sorted.length; i++) {
if (sorted[i][1] >= operation.memorySize) {
return sorted[i][0];
}
}
return -1;
}
Process() {
if (this._nextOperation >= this._operations.value.length)
return;
let operation = this._operations.value[this._nextOperation];
if (operation.type === 'request' && operation.memorySize === 0) {
this._nextOperation++;
return;
}
this._nextOperation++;
let process = Ram.OperationToProcess(operation);
if (operation.type === 'release') {
let index = this._memory.value.findIndex((value) => value.Type === "Process" && value.ProcessID == process.ProcessID);
if (index == -1) {
throw Error("Process not found");
}
if (process.MemorySize !== this._memory.value[index].MemorySize) {
console.warn("Memory size not match");
process.MemorySize = this._memory.value[index].MemorySize;
}
this._releaseHistory.set(process.ProcessID, this._memory.value[index].MemorySize);
let space = new Block(this._memory.value[index].ProcessID, process.MemorySize, "Space");
space.Start = this._memory.value[index].Start;
this._memory.value[index] = space;
if (index > 0 && this._memory.value[index - 1].Type === "Space") {
let prev = this._memory.value[index - 1];
prev.MemorySize += space.MemorySize;
this._memory.value.splice(index, 1);
index--;
space = prev;
}
if (index < this._memory.value.length - 1 && this._memory.value[index + 1].Type === "Space") {
let next = this._memory.value[index + 1];
space.MemorySize += next.MemorySize;
this._memory.value.splice(index + 1, 1);
}
return;
}
let index = this._selectors[this._currentSelector](operation);
if (index == -1) {
throw Error("No enough memory");
}
let space = this._memory.value[index];
process.Start = space.Start;
if (space.MemorySize == process.MemorySize) {
this._memory.value.splice(index, 1);
} else {
space.Start += process.MemorySize;
space.MemorySize -= process.MemorySize;
}
this._memory.value.push(process);
this._memory.value.sort((a, b) => a.Start - b.Start);
}
}
export class Block {
ProcessID = 0;
MemorySize = 0;
Start = 0;
Type: "Process" | "Space" = "Process";
constructor(processID: number, memorySize: number, type: "Process" | "Space" = "Process") {
this.ProcessID = processID;
this.MemorySize = memorySize;
this.Type = type;
}
static Clone(block: Block) {
let newBlock = new Block(block.ProcessID, block.MemorySize, block.Type);
newBlock.Start = block.Start;
return newBlock;
}
}

140
src/logical/expr2/disk.ts Normal file
View File

@ -0,0 +1,140 @@
import {watch} from "vue";
import useExpr2Store from "@/store/expr2/expr2Store.ts";
import {storeToRefs} from "pinia";
import {Operation} from "@/store/expr2/defaultOperation.ts";
export enum Algorithm {
FCFS,
SSTF,
SCAN_FirstDown,
SCAN_FirstUp
}
export class Disk {
constructor() {
const {
algorithm,
operations,
originOperations,
startTrack,
trackCount,
failInfo,
tableChanged,
dataChanged
} = storeToRefs(useExpr2Store());
watch(tableChanged, (newVal) => {
if(newVal === false)
return;
failInfo.value = "";
switch (algorithm.value) {
case Algorithm.FCFS:
operations.value = originOperations.value.map(x => x);
break;
case Algorithm.SSTF:
operations.value = this.SSTF(originOperations.value.map(x => x), startTrack.value);
break;
case Algorithm.SCAN_FirstDown:
operations.value = this.SCAN(originOperations.value.map(x => x), startTrack.value, false);
break;
case Algorithm.SCAN_FirstUp:
operations.value = this.SCAN(originOperations.value.map(x => x), startTrack.value, true);
break;
}
for (let i = 0; i < operations.value.length; i++) {
if (operations.value[i].track < 0 || operations.value[i].track > trackCount.value) {
failInfo.value = i + `: track(${operations.value[i].track}) out of range`;
}
}
dataChanged.value = true;
tableChanged.value = false;
});
}
SSTF(operations: Operation[], startTrack: number): Operation[] {
let ans: Operation[] = [];
operations.sort((a, b) => a.track - b.track);
let set: Set<number> = new Set();
function FindTrack(): number {
let track = startTrack;
if (track < operations[0].track)
track = 0;
else if (track > operations[operations.length - 1].track)
track = operations.length - 1;
else
for (let i = 0; i < operations.length - 1; i++) {
if (operations[i].track === track) {
track = i;
break;
} else if (operations[i].track <= track && track <= operations[i + 1].track) {
track = track - operations[i].track < operations[i + 1].track - track ? i : i + 1;
break;
}
}
return track;
}
let track = FindTrack();
ans.push(operations[track])
set.add(track);
//O(n)
while (set.size < operations.length) {
let candidate: number[] = [];
let left = track, right = track;
while (left >= 0) {
if (set.has(left))
left--;
else {
candidate.push(left);
break;
}
}
while (right <= operations.length - 1) {
if (set.has(right))
right++;
else {
candidate.push(right);
break;
}
}
let min = Number.MAX_VALUE;
for (let t of candidate) {
if (Math.abs(operations[t].track - operations[track].track) < min) {
min = Math.abs(operations[t].track - operations[track].track);
track = t;
}
}
ans.push(operations[track]);
set.add(track);
}
return ans;
}
SCAN(operations: Operation[], startTrack: number, direction: boolean): Operation[] {
let ans: Operation[] = [];
operations.sort((a, b) => a.track - b.track);
let track;
if(direction)
track = operations.findIndex(x => x.track >= startTrack);
else
track = operations.length - 1 - operations.slice().reverse().findIndex(x => x.track <= startTrack);
if(track === -1)
track = operations.length - 1;
if(track === operations.length)
track = 0;
startTrack = track;
ans.push(operations[track]);
while (track + (direction ? 1 : -1) >= 0 && track + (direction ? 1 : -1) < operations.length) {
track += (direction) ? 1 : -1;
ans.push(operations[track]);
}
track = startTrack;
while (track + (direction ? -1 : 1) >= 0 && track + (direction ? -1 : 1) < operations.length) {
track += (direction) ? -1 : 1;
ans.push(operations[track]);
}
return ans;
}
}

13
src/main.ts Normal file
View File

@ -0,0 +1,13 @@
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router.ts'
import TDesign from 'tdesign-vue-next'
import 'tdesign-vue-next/es/style/index.css'
import {createPinia} from "pinia";
document.documentElement.setAttribute('theme-mode', 'dark');
createApp(App)
.use(router)
.use(TDesign)
.use(createPinia())
.mount('#app')

19
src/router.ts Normal file
View File

@ -0,0 +1,19 @@
import {createRouter, createWebHashHistory} from 'vue-router'
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
redirect: '/expr1'
},
{
path: '/expr1',
component: () => import('@/components/expr1.vue')
},
{
path: '/expr2',
component: () => import('@/components/expr2.vue')
}
]
})
export default router;

View File

@ -0,0 +1,20 @@
const operations: Operation[] = [
{ id: 1, memorySize: 200, type: 'request' },
{ id: 2, memorySize: 200, type: 'request' },
{ id: 3, memorySize: 300, type: 'request' },
{ id: 4, memorySize: 200, type: 'request' },
{ id: 5, memorySize: 100, type: 'request' },
{ id: 1, memorySize: 200, type: 'release' },
{ id: 3, memorySize: 300, type: 'release' },
{ id: 5, memorySize: 100, type: 'release' },
{ id: 6, memorySize: 50, type: 'request' },
// { id: 4, memorySize: 450, type: 'release' },
// { id: 5, memorySize: 500, type: 'request' },
// { id: 5, memorySize: 550, type: 'release' },
];
export default operations;
export interface Operation {
id: number;
memorySize: number;
type: 'request' | 'release';
}

View File

@ -0,0 +1,16 @@
import {defineStore} from "pinia";
import operations from '@/store/expr1/defaultOperation.ts'
const useExpr1Store = defineStore('expr1', {
state: () => ({
operations: operations,
memSize: 1000,
detailMemoryIndex: -1,
currentState: operations.length,
tableChanged: false,
failInfo: ''
}),
getters: {},
actions: {}
})
export default useExpr1Store;

View File

@ -0,0 +1,17 @@
const operations: Operation[] = [
{id: 1, track: 80},
{id: 2, track: 60},
{id: 3, track: 90},
{id: 4, track: 70},
{id: 5, track: 10},
{id: 6, track: 30},
{id: 7, track: 20},
{id: 8, track: 99},
{id: 9, track: 50},
];
export default operations;
export interface Operation {
id: number;
track: number;
}

View File

@ -0,0 +1,25 @@
import {defineStore} from "pinia";
import operations from "@/store/expr2/defaultOperation.ts";
import {Algorithm} from "@/logical/expr2/disk.ts";
const useExpr2Store = defineStore('expr2', {
state: () => ({
trackCount: 100,
currentTrack: 0,
startTrack: 50,
operations: operations,
originOperations: operations,
tableChanged: false,
dataChanged: false,
algorithm: Algorithm.FCFS,
failInfo: "",
trackFromIndex: 0,
trackToIndex: 0,
selectTrackFromIndex: 0,
selectTrackToIndex: 0
}),
getters: {},
actions: {}
})
export default useExpr2Store;

26
src/style.css Normal file
View File

@ -0,0 +1,26 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
}

1
src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

33
tsconfig.app.json Normal file
View File

@ -0,0 +1,33 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "preserve",
"jsxImportSource": "vue",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
"baseUrl": "./",
"paths": {
"@/*": [
"./src/*"
]
}
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}

7
tsconfig.json Normal file
View File

@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

24
tsconfig.node.json Normal file
View File

@ -0,0 +1,24 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}

15
vite.config.ts Normal file
View File

@ -0,0 +1,15 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
// https://vite.dev/config/
export default defineConfig({
plugins: [vue(),
vueJsx()
],
resolve: {
alias: {
'@': '/src'
}
}
})